# 发票模块架构重新整理总结 ## 概述 根据DDD(领域驱动设计)架构规范,重新整理了发票模块的架构,明确了各层的职责分工和写法规范。通过这次重新整理,实现了更清晰的架构分层、更规范的代码组织和更好的可维护性。 ## 架构分层和职责 ### 1. 领域服务层(Domain Service Layer) #### 职责定位 - **纯业务规则处理**:只处理领域内的业务规则和计算逻辑 - **无外部依赖**:不依赖仓储、外部服务或其他领域 - **可测试性强**:纯函数式设计,易于单元测试 #### `InvoiceDomainService` 接口设计 ```go type InvoiceDomainService interface { // 验证发票信息完整性 ValidateInvoiceInfo(ctx context.Context, info *value_objects.InvoiceInfo, invoiceType value_objects.InvoiceType) error // 验证开票金额是否合法(基于业务规则) ValidateInvoiceAmount(ctx context.Context, amount decimal.Decimal, availableAmount decimal.Decimal) error // 计算可开票金额(纯计算逻辑) CalculateAvailableAmount(totalRecharged decimal.Decimal, totalGifted decimal.Decimal, totalInvoiced decimal.Decimal) decimal.Decimal // 验证发票申请状态转换 ValidateStatusTransition(currentStatus entities.ApplicationStatus, targetStatus entities.ApplicationStatus) error // 验证发票申请业务规则 ValidateInvoiceApplication(ctx context.Context, application *entities.InvoiceApplication) error } ``` #### 实现特点 - **无状态设计**:不保存任何状态,所有方法都是纯函数 - **参数化输入**:所有需要的数据都通过参数传入 - **业务规则集中**:所有业务规则都在领域服务中定义 - **可复用性强**:可以在不同场景下复用 ### 2. 聚合服务层(Aggregate Service Layer) #### 职责定位 - **聚合根生命周期管理**:协调聚合根的创建、更新、删除 - **业务流程编排**:按照业务规则编排操作流程 - **领域事件发布**:发布领域事件,实现事件驱动架构 - **状态转换控制**:控制聚合根的状态转换 #### `InvoiceAggregateService` 接口设计 ```go type InvoiceAggregateService interface { // 申请开票 ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*entities.InvoiceApplication, error) // 通过发票申请(上传发票) ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error // 拒绝发票申请 RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error } ``` #### 实现特点 - **依赖领域服务**:调用领域服务进行业务规则验证 - **依赖仓储**:通过仓储进行数据持久化 - **发布事件**:发布领域事件,实现松耦合 - **事务边界**:每个方法都是一个事务边界 ### 3. 应用服务层(Application Service Layer) #### 职责定位 - **跨域协调**:协调不同领域之间的交互 - **数据聚合**:聚合来自不同领域的数据 - **事务管理**:管理跨领域的事务 - **外部服务调用**:调用外部服务(如文件存储、邮件服务等) #### `InvoiceApplicationService` 接口设计 ```go type InvoiceApplicationService interface { // ApplyInvoice 申请开票 ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error) // GetUserInvoiceInfo 获取用户发票信息 GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) // UpdateUserInvoiceInfo 更新用户发票信息 UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error // GetUserInvoiceRecords 获取用户开票记录 GetUserInvoiceRecords(ctx context.Context, userID string, req GetInvoiceRecordsRequest) (*dto.InvoiceRecordsResponse, error) // DownloadInvoiceFile 下载发票文件 DownloadInvoiceFile(ctx context.Context, userID string, applicationID string) (*dto.FileDownloadResponse, error) // GetAvailableAmount 获取可开票金额 GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) } ``` #### 实现特点 - **跨域协调**:协调User领域和Finance领域的交互 - **数据聚合**:聚合用户信息、企业认证信息、开票信息等 - **业务编排**:按照业务流程编排各个步骤 - **错误处理**:统一处理跨域错误和业务异常 ## 架构优势 ### 1. 职责分离清晰 - **领域服务**:专注于业务规则,无外部依赖 - **聚合服务**:专注于聚合根生命周期,发布领域事件 - **应用服务**:专注于跨域协调,数据聚合 ### 2. 可测试性强 - **领域服务**:纯函数设计,易于单元测试 - **聚合服务**:可以Mock依赖,进行集成测试 - **应用服务**:可以Mock外部服务,进行端到端测试 ### 3. 可维护性高 - **单一职责**:每个服务都有明确的职责 - **依赖清晰**:依赖关系明确,易于理解 - **扩展性好**:新增功能时只需要修改相应的服务层 ### 4. 符合DDD规范 - **领域边界**:严格遵循领域边界 - **依赖方向**:依赖方向正确,从外到内 - **聚合根设计**:聚合根设计合理,状态转换清晰 ## 代码示例 ### 1. 领域服务实现 ```go // ValidateInvoiceAmount 验证开票金额是否合法(基于业务规则) func (s *InvoiceDomainServiceImpl) ValidateInvoiceAmount(ctx context.Context, amount decimal.Decimal, availableAmount decimal.Decimal) error { if amount.LessThanOrEqual(decimal.Zero) { return errors.New("开票金额必须大于0") } if amount.GreaterThan(availableAmount) { return fmt.Errorf("开票金额不能超过可开票金额,可开票金额:%s", availableAmount.String()) } // 最小开票金额限制 minAmount := decimal.NewFromInt(100) // 最小100元 if amount.LessThan(minAmount) { return fmt.Errorf("开票金额不能少于%s元", minAmount.String()) } return nil } ``` ### 2. 聚合服务实现 ```go // ApplyInvoice 申请开票 func (s *InvoiceAggregateServiceImpl) ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*entities.InvoiceApplication, error) { // 1. 解析金额 amount, err := decimal.NewFromString(req.Amount) if err != nil { return nil, fmt.Errorf("无效的金额格式: %w", err) } // 2. 验证发票信息 if err := s.domainService.ValidateInvoiceInfo(ctx, req.InvoiceInfo, req.InvoiceType); err != nil { return nil, fmt.Errorf("发票信息验证失败: %w", err) } // 3. 获取用户开票信息 userInvoiceInfo, err := s.userInvoiceInfoRepo.FindByUserID(ctx, userID) if err != nil { return nil, fmt.Errorf("获取用户开票信息失败: %w", err) } if userInvoiceInfo == nil { return nil, fmt.Errorf("用户开票信息不存在") } // 4. 创建发票申请聚合根 application := entities.NewInvoiceApplication(userID, req.InvoiceType, amount, userInvoiceInfo.ID) // 5. 设置开票信息快照 application.SetInvoiceInfoSnapshot(req.InvoiceInfo) // 6. 验证聚合根业务规则 if err := s.domainService.ValidateInvoiceApplication(ctx, application); err != nil { return nil, fmt.Errorf("发票申请业务规则验证失败: %w", err) } // 7. 保存聚合根 if err := s.applicationRepo.Create(ctx, application); err != nil { return nil, fmt.Errorf("保存发票申请失败: %w", err) } // 8. 发布领域事件 event := events.NewInvoiceApplicationCreatedEvent( application.ID, application.UserID, application.InvoiceType, application.Amount, application.CompanyName, application.ReceivingEmail, ) if err := s.eventPublisher.PublishInvoiceApplicationCreated(ctx, event); err != nil { fmt.Printf("发布发票申请创建事件失败: %v\n", err) } return application, nil } ``` ### 3. 应用服务实现 ```go // ApplyInvoice 申请开票 func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error) { // 1. 验证用户是否存在 user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, err } if user.ID == "" { return nil, fmt.Errorf("用户不存在") } // 2. 验证发票类型 invoiceType := value_objects.InvoiceType(req.InvoiceType) if !invoiceType.IsValid() { return nil, fmt.Errorf("无效的发票类型") } // 3. 获取用户企业认证信息 userWithEnterprise, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID) if err != nil { return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err) } // 4. 检查用户是否有企业认证信息 if userWithEnterprise.EnterpriseInfo == nil { return nil, fmt.Errorf("用户未完成企业认证,无法申请开票") } // 5. 获取用户开票信息 userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo( ctx, userID, userWithEnterprise.EnterpriseInfo.CompanyName, userWithEnterprise.EnterpriseInfo.UnifiedSocialCode, ) if err != nil { return nil, err } // 6. 验证开票信息完整性 invoiceInfo := value_objects.NewInvoiceInfo( userInvoiceInfo.CompanyName, userInvoiceInfo.TaxpayerID, userInvoiceInfo.BankName, userInvoiceInfo.BankAccount, userInvoiceInfo.CompanyAddress, userInvoiceInfo.CompanyPhone, userInvoiceInfo.ReceivingEmail, ) if err := s.userInvoiceInfoService.ValidateInvoiceInfo(ctx, invoiceInfo, invoiceType); err != nil { return nil, err } // 7. 计算可开票金额 availableAmount, err := s.calculateAvailableAmount(ctx, userID) if err != nil { return nil, fmt.Errorf("计算可开票金额失败: %w", err) } // 8. 验证开票金额 amount, err := decimal.NewFromString(req.Amount) if err != nil { return nil, fmt.Errorf("无效的金额格式: %w", err) } if err := s.invoiceDomainService.ValidateInvoiceAmount(ctx, amount, availableAmount); err != nil { return nil, err } // 9. 调用聚合服务申请开票 aggregateReq := services.ApplyInvoiceRequest{ InvoiceType: invoiceType, Amount: req.Amount, InvoiceInfo: invoiceInfo, } application, err := s.invoiceAggregateService.ApplyInvoice(ctx, userID, aggregateReq) if err != nil { return nil, err } // 10. 构建响应DTO return &dto.InvoiceApplicationResponse{ ID: application.ID, UserID: application.UserID, InvoiceType: application.InvoiceType, Amount: application.Amount, Status: application.Status, InvoiceInfo: invoiceInfo, CreatedAt: application.CreatedAt, }, nil } ``` ## 依赖注入配置 ### 1. 领域服务配置 ```go // 发票领域服务 fx.Annotate( finance_service.NewInvoiceDomainService, fx.ResultTags(`name:"domainService"`), ), ``` ### 2. 聚合服务配置 ```go // 发票聚合服务 fx.Annotate( finance_service.NewInvoiceAggregateService, fx.ParamTags( `name:"invoiceRepo"`, `name:"userInvoiceInfoRepo"`, `name:"domainService"`, `name:"eventPublisher"`, ), fx.ResultTags(`name:"aggregateService"`), ), ``` ### 3. 应用服务配置 ```go // 发票应用服务 fx.Annotate( finance.NewInvoiceApplicationService, fx.As(new(finance.InvoiceApplicationService)), fx.ParamTags( `name:"invoiceRepo"`, `name:"userInvoiceInfoRepo"`, `name:"userRepo"`, `name:"userAggregateService"`, `name:"rechargeRecordRepo"`, `name:"walletRepo"`, `name:"domainService"`, `name:"aggregateService"`, `name:"userInvoiceInfoService"`, `name:"storageService"`, `name:"logger"`, ), ), ``` ## 总结 通过这次重新整理,我们实现了: 1. ✅ **架构规范**:严格遵循DDD架构规范,职责分离清晰 2. ✅ **代码质量**:代码结构清晰,易于理解和维护 3. ✅ **可测试性**:各层都可以独立测试,测试覆盖率高 4. ✅ **可扩展性**:新增功能时只需要修改相应的服务层 5. ✅ **业务逻辑**:业务逻辑清晰,流程明确 这种架构设计既满足了业务需求,又符合DDD架构规范,是一个优秀的架构实现。