diff --git a/apps/product-query-svc/README.md b/apps/product-query-svc/README.md index bc2656b..33c03b2 100644 --- a/apps/product-query-svc/README.md +++ b/apps/product-query-svc/README.md @@ -4,12 +4,12 @@ - domain:领域模型与规则(不依赖外层) - ports:核心对外边界 - - inbound(use case 接口):`ProductService` - - outbound(基础设施接口):`ProductRepo` -- app:用例实现(编排业务,依赖 `ports`,不关心 HTTP/DB) + - inbound(use case 接口):`ProductUseCases`、`UserQueries` + - outbound(基础设施接口):`ProductRepository`、`UserRepository` +- application:用例实现(按聚合拆分到 `application/product` 与 `application/user`,依赖 ports,脱离 HTTP/DB) - adapters:适配器实现 - - inbound/http:实现 OpenAPI 生成的 `ServerInterface`,调用 `ports.ProductService` - - outbound/inmem、outbound/postgres:实现 `ports.ProductRepo` + - inbound/http:实现 OpenAPI 生成的 `ServerInterface`,调用 `ports/inbound.ProductUseCases` + - outbound/inmem、outbound/postgres:实现 `ports/outbound.ProductRepository` - backend/cmd/.../main.go:组装根,选择具体适配器(inmem 或 postgres),注入到 app 层,再挂到 HTTP。 依赖方向:`adapters -> app -> ports <- domain`(领域最内层,适配器最外层)。 @@ -23,15 +23,15 @@ Client ↓ HTTP Inbound Adapter apps/product-query-svc/adapters/inbound/http/handler_product_read.go (Server.GetProductByID) - ↓ 依赖入站端口 ports.ProductService + ↓ 依赖入站端口 ports/inbound.ProductUseCases Ports (Inbound) - apps/product-query-svc/ports/inbound.go (interface ProductService) - ↓ 由组装根注入 app 实现 + apps/product-query-svc/ports/inbound/product.go (interface ProductUseCases) + ↓ 由组装根注入 application 实现 Application (Use Case) - apps/product-query-svc/app/product_service.go (ProductService.GetProduct) - ↓ 依赖出站端口 ports.ProductRepo + apps/product-query-svc/application/product/service.go (Service.FetchByID) + ↓ 依赖出站端口 ports/outbound.ProductRepository Ports (Outbound) - apps/product-query-svc/ports/outbound.go (interface ProductRepo) + apps/product-query-svc/ports/outbound/product.go (interface ProductRepository) ↓ 由组装根选择并注入具体适配器 Outbound Adapters (Persistence) ├─ apps/product-query-svc/adapters/outbound/inmem/product_repository.go (InMemRepo.GetByID) @@ -42,8 +42,8 @@ Domain Composition Root(组装根) backend/cmd/marketplace/product-query-svc/main.go - - 读取配置,选择 inmem 或 postgres 作为 ProductRepo 的实现 - - 构造 app.ProductService,并作为 ports.ProductService 注入 HTTP 适配器 + - 读取配置,选择 inmem 或 postgres 作为 ProductRepository 的实现 + - 构造 productapp.Service,并作为 ports/inbound.ProductUseCases 注入 HTTP 适配器 - 启动 HTTP 服务器 ``` @@ -55,16 +55,16 @@ Client HTTP Inbound Adapter apps/product-query-svc/adapters/inbound/http/handler_product_write.go (Server.CreateProduct) - 将 JSON DTO 映射为 domain.Product(美元转分,调用 NewProduct 校验不变式) - ↓ 调用入站端口 ports.ProductService.CreateProduct + ↓ 调用入站端口 ports/inbound.ProductUseCases.Create Application (Use Case) - apps/product-query-svc/app/product_service.go (CreateProduct) + apps/product-query-svc/application/product/service.go (Service.Create) - 调用 p.Validate / 富行为 → 通过出站端口持久化 - ↓ +↓ Ports (Outbound) - apps/product-query-svc/ports/outbound.go (ProductRepo.Create) + apps/product-query-svc/ports/outbound/product.go (ProductRepository.Create) ↓ Outbound Adapter - apps/product-query-svc/adapters/outbound/postgres/inmem (真正落库/内存存储) + apps/product-query-svc/adapters/outbound/postgres|inmem (真正落库/内存存储) ↓ 返回 HTTP(Created + JSON),领域错误映射为 400/404。 ``` diff --git a/apps/product-query-svc/adapters/inbound/http/handler_product_read.go b/apps/product-query-svc/adapters/inbound/http/handler_product_read.go index 3eddc4d..54b5305 100644 --- a/apps/product-query-svc/adapters/inbound/http/handler_product_read.go +++ b/apps/product-query-svc/adapters/inbound/http/handler_product_read.go @@ -7,7 +7,7 @@ func (s *Server) GetProductByID(w http.ResponseWriter, r *http.Request, id int64 writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer") return } - p, err := s.products.GetProduct(r.Context(), id) + p, err := s.products.FetchByID(r.Context(), id) if err != nil { writeDomainError(w, err) return @@ -33,7 +33,7 @@ func (s *Server) SearchProducts(w http.ResponseWriter, r *http.Request, params S if params.PageSize != nil { pageSize = *params.PageSize } - items, total, err := s.products.SearchProducts(r.Context(), q, page, pageSize) + items, total, err := s.products.Search(r.Context(), q, page, pageSize) if err != nil { writeDomainError(w, err) return diff --git a/apps/product-query-svc/adapters/inbound/http/handler_product_write.go b/apps/product-query-svc/adapters/inbound/http/handler_product_write.go index 48d4488..67dd04a 100644 --- a/apps/product-query-svc/adapters/inbound/http/handler_product_write.go +++ b/apps/product-query-svc/adapters/inbound/http/handler_product_write.go @@ -13,7 +13,7 @@ func (s *Server) DeleteProductByID(w http.ResponseWriter, r *http.Request, id in writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer") return } - if err := s.products.DeleteProduct(r.Context(), id); err != nil { + if err := s.products.Remove(r.Context(), id); err != nil { writeDomainError(w, err) return } @@ -33,7 +33,7 @@ func (s *Server) CreateProduct(w http.ResponseWriter, r *http.Request) { writeDomainError(w, err) return } - id, err := s.products.CreateProduct(r.Context(), p) + id, err := s.products.Create(r.Context(), p) if err != nil { writeDomainError(w, err) return @@ -61,7 +61,7 @@ func (s *Server) UpdateProduct(w http.ResponseWriter, r *http.Request, id int64) return } p.ID = id - updated, err := s.products.UpdateProduct(r.Context(), p) + updated, err := s.products.Update(r.Context(), p) if err != nil { writeDomainError(w, err) return diff --git a/apps/product-query-svc/adapters/inbound/http/handler_user_read.go b/apps/product-query-svc/adapters/inbound/http/handler_user_read.go index f90a7ab..4e68a0e 100644 --- a/apps/product-query-svc/adapters/inbound/http/handler_user_read.go +++ b/apps/product-query-svc/adapters/inbound/http/handler_user_read.go @@ -7,7 +7,7 @@ func (s *Server) GetUserByID(w http.ResponseWriter, r *http.Request, id int64) { writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer") return } - u, err := s.users.GetUser(r.Context(), id) + u, err := s.users.FetchByID(r.Context(), id) if err != nil { writeDomainError(w, err) return diff --git a/apps/product-query-svc/adapters/inbound/http/server.go b/apps/product-query-svc/adapters/inbound/http/server.go index a479e90..7e93ae8 100644 --- a/apps/product-query-svc/adapters/inbound/http/server.go +++ b/apps/product-query-svc/adapters/inbound/http/server.go @@ -3,16 +3,16 @@ package httpadapter import ( "net/http" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/inbound" ) // Server wires product and user services to HTTP handlers generated from OpenAPI. type Server struct { - products ports.ProductService - users ports.UserService + products inbound.ProductUseCases + users inbound.UserQueries } -func NewServer(products ports.ProductService, users ports.UserService) *Server { +func NewServer(products inbound.ProductUseCases, users inbound.UserQueries) *Server { return &Server{products: products, users: users} } diff --git a/apps/product-query-svc/adapters/outbound/inmem/product_repository.go b/apps/product-query-svc/adapters/outbound/inmem/product_repository.go index 71e1d82..310e8ca 100644 --- a/apps/product-query-svc/adapters/outbound/inmem/product_repository.go +++ b/apps/product-query-svc/adapters/outbound/inmem/product_repository.go @@ -7,6 +7,12 @@ import ( "time" "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" +) + +var ( + _ outbound.ProductRepository = (*InMemRepo)(nil) + _ outbound.UserRepository = (*InMemRepo)(nil) ) // 简单的内存实现,用于本地开发/测试和示例 wiring @@ -101,7 +107,7 @@ func (r *InMemRepo) Update(ctx context.Context, p *domain.Product) error { return nil } -func (r *InMemRepo) GetUserByID(ctx context.Context, id int64) (*domain.User, error) { +func (r *InMemRepo) FindByID(ctx context.Context, id int64) (*domain.User, error) { r.mu.RLock() defer r.mu.RUnlock() u, ok := r.users[id] diff --git a/apps/product-query-svc/adapters/outbound/postgres/product_repository.go b/apps/product-query-svc/adapters/outbound/postgres/product_repository.go index c8ebb0d..28cf176 100644 --- a/apps/product-query-svc/adapters/outbound/postgres/product_repository.go +++ b/apps/product-query-svc/adapters/outbound/postgres/product_repository.go @@ -7,7 +7,7 @@ import ( "github.com/Masterminds/squirrel" "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -16,7 +16,11 @@ var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) type PGProductRepo struct{ pool *pgxpool.Pool } -func NewProductRepository(pool *pgxpool.Pool) ports.ProductRepo { return &PGProductRepo{pool: pool} } +var _ outbound.ProductRepository = (*PGProductRepo)(nil) + +func NewProductRepository(pool *pgxpool.Pool) outbound.ProductRepository { + return &PGProductRepo{pool: pool} +} func (r *PGProductRepo) GetByID(ctx context.Context, id int64) (*domain.Product, error) { q, args, err := psql.Select("id", "name", "price", "tags").From("products").Where(squirrel.Eq{"id": id}).ToSql() diff --git a/apps/product-query-svc/adapters/outbound/postgres/user_repository.go b/apps/product-query-svc/adapters/outbound/postgres/user_repository.go index 6bd0e6c..71ca7ae 100644 --- a/apps/product-query-svc/adapters/outbound/postgres/user_repository.go +++ b/apps/product-query-svc/adapters/outbound/postgres/user_repository.go @@ -6,16 +6,18 @@ import ( "github.com/Masterminds/squirrel" "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type PGUserRepo struct{ pool *pgxpool.Pool } -func NewUserRepository(pool *pgxpool.Pool) ports.UserRepo { return &PGUserRepo{pool: pool} } +var _ outbound.UserRepository = (*PGUserRepo)(nil) -func (r *PGUserRepo) GetUserByID(ctx context.Context, id int64) (*domain.User, error) { +func NewUserRepository(pool *pgxpool.Pool) outbound.UserRepository { return &PGUserRepo{pool: pool} } + +func (r *PGUserRepo) FindByID(ctx context.Context, id int64) (*domain.User, error) { q, args, err := psql.Select("id", "name", "email", "created_at").From("users").Where(squirrel.Eq{"id": id}).ToSql() if err != nil { return nil, err diff --git a/apps/product-query-svc/app/product_service.go b/apps/product-query-svc/app/product_service.go deleted file mode 100644 index 29a8e52..0000000 --- a/apps/product-query-svc/app/product_service.go +++ /dev/null @@ -1,45 +0,0 @@ -package app - -import ( - "context" - "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" -) - -type ProductService struct { - repo ports.ProductRepo -} - -func NewProductService(r ports.ProductRepo) *ProductService { return &ProductService{repo: r} } - -func (s *ProductService) GetProduct(ctx context.Context, id int64) (*domain.Product, error) { - return s.repo.GetByID(ctx, id) -} - -func (s *ProductService) SearchProducts(ctx context.Context, q string, page, pageSize int) ([]domain.Product, int, error) { - return s.repo.Search(ctx, q, page, pageSize) -} - -func (s *ProductService) DeleteProduct(ctx context.Context, id int64) error { - return s.repo.Delete(ctx, id) -} - -func (s *ProductService) CreateProduct(ctx context.Context, p *domain.Product) (int64, error) { - if err := p.Validate(); err != nil { - return 0, err - } - return s.repo.Create(ctx, p) -} - -func (s *ProductService) UpdateProduct(ctx context.Context, p *domain.Product) (*domain.Product, error) { - if p.ID <= 0 { - return nil, domain.ErrValidation - } - if err := p.Validate(); err != nil { - return nil, err - } - if err := s.repo.Update(ctx, p); err != nil { - return nil, err - } - return s.repo.GetByID(ctx, p.ID) -} diff --git a/apps/product-query-svc/app/user_service.go b/apps/product-query-svc/app/user_service.go deleted file mode 100644 index 8a8440b..0000000 --- a/apps/product-query-svc/app/user_service.go +++ /dev/null @@ -1,22 +0,0 @@ -package app - -import ( - "context" - "errors" - - "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" -) - -type UserService struct { - repo ports.UserRepo -} - -func NewUserService(r ports.UserRepo) *UserService { return &UserService{repo: r} } - -func (s *UserService) GetUser(ctx context.Context, id int64) (*domain.User, error) { - if id <= 0 { - return nil, errors.Join(domain.ErrValidation, errors.New("id must be a positive integer")) - } - return s.repo.GetUserByID(ctx, id) -} diff --git a/apps/product-query-svc/application/product/service.go b/apps/product-query-svc/application/product/service.go new file mode 100644 index 0000000..9f7a222 --- /dev/null +++ b/apps/product-query-svc/application/product/service.go @@ -0,0 +1,52 @@ +package productapp + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/inbound" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" +) + +var _ inbound.ProductUseCases = (*Service)(nil) + +// Service orchestrates product-related use cases across outbound dependencies. +type Service struct { + repository outbound.ProductRepository +} + +func NewService(repository outbound.ProductRepository) *Service { + return &Service{repository: repository} +} + +func (s *Service) FetchByID(ctx context.Context, id int64) (*domain.Product, error) { + return s.repository.GetByID(ctx, id) +} + +func (s *Service) Search(ctx context.Context, query string, page, pageSize int) ([]domain.Product, int, error) { + return s.repository.Search(ctx, query, page, pageSize) +} + +func (s *Service) Remove(ctx context.Context, id int64) error { + return s.repository.Delete(ctx, id) +} + +func (s *Service) Create(ctx context.Context, product *domain.Product) (int64, error) { + if err := product.Validate(); err != nil { + return 0, err + } + return s.repository.Create(ctx, product) +} + +func (s *Service) Update(ctx context.Context, product *domain.Product) (*domain.Product, error) { + if product.ID <= 0 { + return nil, domain.ValidationError("id must be a positive integer") + } + if err := product.Validate(); err != nil { + return nil, err + } + if err := s.repository.Update(ctx, product); err != nil { + return nil, err + } + return s.repository.GetByID(ctx, product.ID) +} diff --git a/apps/product-query-svc/application/user/service.go b/apps/product-query-svc/application/user/service.go new file mode 100644 index 0000000..2d3cfad --- /dev/null +++ b/apps/product-query-svc/application/user/service.go @@ -0,0 +1,27 @@ +package userapp + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/inbound" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" +) + +var _ inbound.UserQueries = (*Service)(nil) + +// Service exposes user-specific use cases backed by a persistent repository. +type Service struct { + repository outbound.UserRepository +} + +func NewService(repository outbound.UserRepository) *Service { + return &Service{repository: repository} +} + +func (s *Service) FetchByID(ctx context.Context, id int64) (*domain.User, error) { + if id <= 0 { + return nil, domain.ValidationError("id must be a positive integer") + } + return s.repository.FindByID(ctx, id) +} diff --git a/apps/product-query-svc/domain/errors.go b/apps/product-query-svc/domain/errors.go new file mode 100644 index 0000000..cf33e5c --- /dev/null +++ b/apps/product-query-svc/domain/errors.go @@ -0,0 +1,13 @@ +package domain + +import "errors" + +var ( + ErrValidation = errors.New("validation error") + ErrNotFound = errors.New("not found") +) + +// ValidationError wraps ErrValidation with a more specific message. +func ValidationError(msg string) error { + return errors.Join(ErrValidation, errors.New(msg)) +} diff --git a/apps/product-query-svc/domain/product.go b/apps/product-query-svc/domain/product.go index f448341..cad0030 100644 --- a/apps/product-query-svc/domain/product.go +++ b/apps/product-query-svc/domain/product.go @@ -1,15 +1,6 @@ package domain -import ( - "errors" - "strings" -) - -// 领域错误(供适配器映射状态码) -var ( - ErrValidation = errors.New("validation error") - ErrNotFound = errors.New("not found") -) +import "strings" // Product 是领域聚合根,Price 以分为单位避免浮点误差。 type Product struct { @@ -36,13 +27,13 @@ func NewProduct(name string, priceCents int64, tags []string) (*Product, error) // Validate 检查核心不变式。 func (p *Product) Validate() error { if p.Name == "" { - return errValidation("name required") + return ValidationError("name required") } if p.Price < 0 { - return errValidation("price must be >= 0") + return ValidationError("price must be >= 0") } if len(p.Tags) > maxTags { - return errValidation("tags exceed limit") + return ValidationError("tags exceed limit") } return nil } @@ -50,7 +41,7 @@ func (p *Product) Validate() error { // ChangePrice 变更价格(分为单位)。 func (p *Product) ChangePrice(newPrice int64) error { if newPrice < 0 { - return errValidation("price must be >= 0") + return ValidationError("price must be >= 0") } p.Price = newPrice return nil @@ -68,7 +59,7 @@ func (p *Product) AddTag(tag string) error { } } if len(p.Tags) >= maxTags { - return errValidation("tags exceed limit") + return ValidationError("tags exceed limit") } p.Tags = append(p.Tags, cleaned) return nil @@ -99,11 +90,6 @@ func (p *Product) replaceTags(tags []string) error { return nil } -// errValidation 构造带细节的校验错误。 -func errValidation(msg string) error { - return errors.Join(ErrValidation, errors.New(msg)) -} - func equalFold(a, b string) bool { return strings.EqualFold(strings.TrimSpace(a), strings.TrimSpace(b)) } @@ -126,7 +112,7 @@ func sanitizeTags(tags []string) ([]string, error) { seen[key] = struct{}{} sanitized = append(sanitized, cleaned) if len(sanitized) > maxTags { - return nil, errValidation("tags exceed limit") + return nil, ValidationError("tags exceed limit") } } if len(sanitized) == 0 { diff --git a/apps/product-query-svc/domain/users.go b/apps/product-query-svc/domain/users.go index 9ba0d27..e80b525 100644 --- a/apps/product-query-svc/domain/users.go +++ b/apps/product-query-svc/domain/users.go @@ -35,10 +35,10 @@ func NewUser(name string, email string) (*User, error) { func (u *User) Validate() error { if u.Name == "" { - return errValidation("name required") + return ValidationError("name required") } if !IsValidEmail(u.Email) { - return errValidation("invalid email format") + return ValidationError("invalid email format") } return nil } @@ -48,7 +48,7 @@ func (u *User) Validate() error { func (u *User) ChangeName(newName string) error { cleaned := strings.TrimSpace(newName) if cleaned == "" { - return errValidation("name required") + return ValidationError("name required") } u.Name = cleaned return nil diff --git a/apps/product-query-svc/ports/inbound.go b/apps/product-query-svc/ports/inbound.go deleted file mode 100644 index 4b39cc9..0000000 --- a/apps/product-query-svc/ports/inbound.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package ports contains the stable boundaries of the core. -// inbound: application-facing use case interfaces -package ports - -import ( - "context" - - "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" -) - -// ProductService is an inbound port exposing application use cases -// to driving adapters (e.g., HTTP, gRPC, CLI). -type ProductService interface { - GetProduct(ctx context.Context, id int64) (*domain.Product, error) - // SearchProducts returns items and total count - SearchProducts(ctx context.Context, q string, page, pageSize int) ([]domain.Product, int, error) - // DeleteProduct removes the product by id - DeleteProduct(ctx context.Context, id int64) error - // CreateProduct validates and persists a new product, returning its id - CreateProduct(ctx context.Context, p *domain.Product) (int64, error) - // UpdateProduct replaces the existing product state and returns the updated snapshot - UpdateProduct(ctx context.Context, p *domain.Product) (*domain.Product, error) -} - -// UserService exposes user-related use cases to driving adapters. -type UserService interface { - GetUser(ctx context.Context, id int64) (*domain.User, error) -} diff --git a/apps/product-query-svc/ports/inbound/product.go b/apps/product-query-svc/ports/inbound/product.go new file mode 100644 index 0000000..5260e98 --- /dev/null +++ b/apps/product-query-svc/ports/inbound/product.go @@ -0,0 +1,16 @@ +package inbound + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" +) + +// ProductUseCases describes the application-facing entrypoints for product interactions. +type ProductUseCases interface { + FetchByID(ctx context.Context, id int64) (*domain.Product, error) + Search(ctx context.Context, query string, page, pageSize int) ([]domain.Product, int, error) + Create(ctx context.Context, product *domain.Product) (int64, error) + Update(ctx context.Context, product *domain.Product) (*domain.Product, error) + Remove(ctx context.Context, id int64) error +} diff --git a/apps/product-query-svc/ports/inbound/user.go b/apps/product-query-svc/ports/inbound/user.go new file mode 100644 index 0000000..af6066d --- /dev/null +++ b/apps/product-query-svc/ports/inbound/user.go @@ -0,0 +1,12 @@ +package inbound + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" +) + +// UserQueries exposes read-oriented user use cases for driving adapters. +type UserQueries interface { + FetchByID(ctx context.Context, id int64) (*domain.User, error) +} diff --git a/apps/product-query-svc/ports/outbound.go b/apps/product-query-svc/ports/outbound.go deleted file mode 100644 index 4c8b44b..0000000 --- a/apps/product-query-svc/ports/outbound.go +++ /dev/null @@ -1,23 +0,0 @@ -// outbound: infrastructure-facing interfaces (repositories, gateways) -package ports - -import ( - "context" - - "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" -) - -// ProductRepo is an outbound port abstracting persistence concerns. -type ProductRepo interface { - GetByID(ctx context.Context, id int64) (*domain.Product, error) - // Search returns the page of items and the total count matching the query - Search(ctx context.Context, q string, page, pageSize int) ([]domain.Product, int, error) - Create(ctx context.Context, p *domain.Product) (int64, error) - Delete(ctx context.Context, id int64) error - Update(ctx context.Context, p *domain.Product) error -} - -// UserRepo abstracts access to persistent user data. -type UserRepo interface { - GetUserByID(ctx context.Context, id int64) (*domain.User, error) -} diff --git a/apps/product-query-svc/ports/outbound/product.go b/apps/product-query-svc/ports/outbound/product.go new file mode 100644 index 0000000..4cf4a91 --- /dev/null +++ b/apps/product-query-svc/ports/outbound/product.go @@ -0,0 +1,16 @@ +package outbound + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" +) + +// ProductRepository abstracts persistence concerns for product aggregates. +type ProductRepository interface { + GetByID(ctx context.Context, id int64) (*domain.Product, error) + Search(ctx context.Context, query string, page, pageSize int) ([]domain.Product, int, error) + Create(ctx context.Context, product *domain.Product) (int64, error) + Update(ctx context.Context, product *domain.Product) error + Delete(ctx context.Context, id int64) error +} diff --git a/apps/product-query-svc/ports/outbound/user.go b/apps/product-query-svc/ports/outbound/user.go new file mode 100644 index 0000000..cdb6ae8 --- /dev/null +++ b/apps/product-query-svc/ports/outbound/user.go @@ -0,0 +1,12 @@ +package outbound + +import ( + "context" + + "github.com/fightingBald/GoTuto/apps/product-query-svc/domain" +) + +// UserRepository abstracts access to persistent user data. +type UserRepository interface { + FindByID(ctx context.Context, id int64) (*domain.User, error) +} diff --git a/backend/cmd/marketplace/product-query-svc/main.go b/backend/cmd/marketplace/product-query-svc/main.go index e2fa348..770079b 100644 --- a/backend/cmd/marketplace/product-query-svc/main.go +++ b/backend/cmd/marketplace/product-query-svc/main.go @@ -13,8 +13,9 @@ import ( appshttp "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/inbound/http" appsinmem "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/outbound/inmem" appspg "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/outbound/postgres" - appsvc "github.com/fightingBald/GoTuto/apps/product-query-svc/app" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" + productapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/product" + userapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/user" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -39,8 +40,8 @@ func main() { log.Println("starting product-query-svc") var ( - repo ports.ProductRepo - userRepo ports.UserRepo + repo outbound.ProductRepository + userRepo outbound.UserRepository pool *pgxpool.Pool ) @@ -64,8 +65,8 @@ func main() { } // build service - productSvc := appsvc.NewProductService(repo) - userSvc := appsvc.NewUserService(userRepo) + productSvc := productapp.NewService(repo) + userSvc := userapp.NewService(userRepo) server := appshttp.NewServer(productSvc, userSvc) diff --git a/internal/testutil/httpserver.go b/internal/testutil/httpserver.go index 78a98cf..2e0d21c 100644 --- a/internal/testutil/httpserver.go +++ b/internal/testutil/httpserver.go @@ -5,22 +5,23 @@ import ( "net/http/httptest" httpadapter "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/inbound/http" - app "github.com/fightingBald/GoTuto/apps/product-query-svc/app" - "github.com/fightingBald/GoTuto/apps/product-query-svc/ports" + productapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/product" + userapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/user" + "github.com/fightingBald/GoTuto/apps/product-query-svc/ports/outbound" "github.com/go-chi/chi/v5" ) // NewHTTPHandler wires repos -> services -> HTTP handler. -func NewHTTPHandler(productRepo ports.ProductRepo, userRepo ports.UserRepo) http.Handler { - productSvc := app.NewProductService(productRepo) - userSvc := app.NewUserService(userRepo) +func NewHTTPHandler(productRepo outbound.ProductRepository, userRepo outbound.UserRepository) http.Handler { + productSvc := productapp.NewService(productRepo) + userSvc := userapp.NewService(userRepo) server := httpadapter.NewServer(productSvc, userSvc) r := chi.NewRouter() return httpadapter.HandlerFromMux(server, r) } // NewHTTPServer starts an httptest.Server for convenience. -func NewHTTPServer(productRepo ports.ProductRepo, userRepo ports.UserRepo) *httptest.Server { +func NewHTTPServer(productRepo outbound.ProductRepository, userRepo outbound.UserRepository) *httptest.Server { h := NewHTTPHandler(productRepo, userRepo) return httptest.NewServer(h) } diff --git a/readme.md b/readme.md index e9aca32..df1adb0 100644 --- a/readme.md +++ b/readme.md @@ -367,7 +367,7 @@ go generate ./api
批次 3 — 核心应用层(domain、ports、app) -- 相关文件:apps/product-query-svc/domain/ apps/product-query-svc/ports/ apps/product-query-svc/app/ +- 相关文件:apps/product-query-svc/domain/ apps/product-query-svc/ports/ apps/product-query-svc/application/ - 建议 commit message:"app: add domain models, service implementation and ports for product-query-svc"
diff --git a/test/http_pg/create_test.go b/test/http_pg/create_test.go index 1567680..9bc3c93 100644 --- a/test/http_pg/create_test.go +++ b/test/http_pg/create_test.go @@ -12,7 +12,8 @@ import ( appshttp "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/inbound/http" appspg "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/outbound/postgres" - appsvc "github.com/fightingBald/GoTuto/apps/product-query-svc/app" + productapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/product" + userapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/user" "github.com/fightingBald/GoTuto/internal/testutil" "github.com/go-chi/chi/v5" ) @@ -30,8 +31,8 @@ func TestCreateProduct_Postgres(t *testing.T) { productRepo := appspg.NewProductRepository(pool) userRepo := appspg.NewUserRepository(pool) - productSvc := appsvc.NewProductService(productRepo) - userSvc := appsvc.NewUserService(userRepo) + productSvc := productapp.NewService(productRepo) + userSvc := userapp.NewService(userRepo) server := appshttp.NewServer(productSvc, userSvc) r := chi.NewRouter() diff --git a/test/http_pg/search_test.go b/test/http_pg/search_test.go index 6baebb4..7a2a7cc 100644 --- a/test/http_pg/search_test.go +++ b/test/http_pg/search_test.go @@ -10,7 +10,8 @@ import ( appshttp "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/inbound/http" appspg "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/outbound/postgres" - appsvc "github.com/fightingBald/GoTuto/apps/product-query-svc/app" + productapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/product" + userapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/user" "github.com/fightingBald/GoTuto/internal/testutil" "github.com/go-chi/chi/v5" ) @@ -29,8 +30,8 @@ func TestSearchProducts_Postgres(t *testing.T) { productRepo := appspg.NewProductRepository(pool) userRepo := appspg.NewUserRepository(pool) - productSvc := appsvc.NewProductService(productRepo) - userSvc := appsvc.NewUserService(userRepo) + productSvc := productapp.NewService(productRepo) + userSvc := userapp.NewService(userRepo) server := appshttp.NewServer(productSvc, userSvc) r := chi.NewRouter() diff --git a/test/http_pg/user_test.go b/test/http_pg/user_test.go index 818fb3a..210a355 100644 --- a/test/http_pg/user_test.go +++ b/test/http_pg/user_test.go @@ -13,7 +13,8 @@ import ( appshttp "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/inbound/http" appspg "github.com/fightingBald/GoTuto/apps/product-query-svc/adapters/outbound/postgres" - appsvc "github.com/fightingBald/GoTuto/apps/product-query-svc/app" + productapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/product" + userapp "github.com/fightingBald/GoTuto/apps/product-query-svc/application/user" "github.com/fightingBald/GoTuto/internal/testutil" "github.com/go-chi/chi/v5" "github.com/jackc/pgconn" @@ -52,8 +53,8 @@ func TestGetUserByID_Postgres(t *testing.T) { productRepo := appspg.NewProductRepository(pool) userRepo := appspg.NewUserRepository(pool) - productSvc := appsvc.NewProductService(productRepo) - userSvc := appsvc.NewUserService(userRepo) + productSvc := productapp.NewService(productRepo) + userSvc := userapp.NewService(userRepo) server := appshttp.NewServer(productSvc, userSvc) r := chi.NewRouter()