Files
tyapi-server/DDD规范企业认证信息自动填充实现总结.md
2025-08-02 02:54:21 +08:00

9.7 KiB
Raw Blame History

DDD规范企业认证信息自动填充实现总结

概述

根据DDD领域驱动设计架构规范重新设计了企业认证信息自动填充功能。在DDD中跨域操作应该通过应用服务层来协调而不是在领域服务层直接操作其他领域的仓储。

DDD架构规范

1. 领域边界原则

  • 领域服务层:只能操作本领域的仓储和实体
  • 应用服务层:负责跨域协调,调用不同领域的服务
  • 聚合根:每个领域有自己的聚合根,不能直接访问其他领域的聚合根

2. 依赖方向

应用服务层 → 领域服务层 → 仓储层
    ↓
跨域协调

重新设计架构

1. 领域服务层Finance Domain

UserInvoiceInfoService接口更新

type UserInvoiceInfoService interface {
    // 基础方法
    GetUserInvoiceInfo(ctx context.Context, userID string) (*entities.UserInvoiceInfo, error)
    CreateOrUpdateUserInvoiceInfo(ctx context.Context, userID string, invoiceInfo *value_objects.InvoiceInfo) (*entities.UserInvoiceInfo, error)
    
    // 新增:包含企业认证信息的方法
    GetUserInvoiceInfoWithEnterpriseInfo(ctx context.Context, userID string, companyName, taxpayerID string) (*entities.UserInvoiceInfo, error)
    CreateOrUpdateUserInvoiceInfoWithEnterpriseInfo(ctx context.Context, userID string, invoiceInfo *value_objects.InvoiceInfo, companyName, taxpayerID string) (*entities.UserInvoiceInfo, error)
    
    ValidateInvoiceInfo(ctx context.Context, invoiceInfo *value_objects.InvoiceInfo, invoiceType value_objects.InvoiceType) error
    DeleteUserInvoiceInfo(ctx context.Context, userID string) error
}

实现特点

  • 移除跨域依赖:不再直接依赖user_repo.UserRepository
  • 参数化设计:通过方法参数接收企业认证信息
  • 保持纯净性:领域服务只处理本领域的业务逻辑

2. 应用服务层Application Layer

InvoiceApplicationService更新

type InvoiceApplicationServiceImpl struct {
    invoiceRepo         finance_repo.InvoiceApplicationRepository
    userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository
    userRepo            user_repo.UserRepository
    userAggregateService user_service.UserAggregateService  // 新增:用户聚合服务
    rechargeRecordRepo  finance_repo.RechargeRecordRepository
    walletRepo          finance_repo.WalletRepository
    invoiceDomainService services.InvoiceDomainService
    invoiceAggregateService services.InvoiceAggregateService
    userInvoiceInfoService services.UserInvoiceInfoService
    storageService      *storage.QiNiuStorageService
    logger              *zap.Logger
}

跨域协调逻辑

func (s *InvoiceApplicationServiceImpl) GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) {
    // 1. 通过用户聚合服务获取企业认证信息
    user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
    if err != nil {
        return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err)
    }

    // 2. 提取企业认证信息
    var companyName, taxpayerID string
    var companyNameReadOnly, taxpayerIDReadOnly bool
    
    if user.EnterpriseInfo != nil {
        companyName = user.EnterpriseInfo.CompanyName
        taxpayerID = user.EnterpriseInfo.UnifiedSocialCode
        companyNameReadOnly = true
        taxpayerIDReadOnly = true
    }

    // 3. 调用领域服务,传入企业认证信息
    userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo(ctx, userID, companyName, taxpayerID)
    if err != nil {
        return nil, err
    }

    // 4. 构建响应DTO
    return &dto.InvoiceInfoResponse{
        CompanyName:    userInvoiceInfo.CompanyName,
        TaxpayerID:     userInvoiceInfo.TaxpayerID,
        // ... 其他字段
        CompanyNameReadOnly: companyNameReadOnly,
        TaxpayerIDReadOnly:  taxpayerIDReadOnly,
    }, nil
}

3. 依赖注入更新

容器配置

// 用户聚合服务
fx.Annotate(
    user_service.NewUserAggregateService,
    fx.ResultTags(`name:"userAggregateService"`),
),

// 用户开票信息服务移除userRepo依赖
fx.Annotate(
    finance_service.NewUserInvoiceInfoService,
    fx.ParamTags(
        `name:"userInvoiceInfoRepo"`,
    ),
    fx.ResultTags(`name:"userInvoiceInfoService"`),
),

// 发票应用服务添加userAggregateService依赖
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. 业务逻辑清晰

  • 数据流向明确:企业认证信息 → 应用服务 → 开票信息
  • 错误处理统一:在应用服务层统一处理跨域错误
  • 权限控制集中:在应用服务层统一控制访问权限

工作流程

1. 获取开票信息流程

用户请求 → 应用服务层
    ↓
调用UserAggregateService.GetUserWithEnterpriseInfo()
    ↓
获取企业认证信息
    ↓
调用UserInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo()
    ↓
返回开票信息(包含企业认证信息)

2. 更新开票信息流程

用户请求 → 应用服务层
    ↓
调用UserAggregateService.GetUserWithEnterpriseInfo()
    ↓
验证企业认证状态
    ↓
调用UserInvoiceInfoService.CreateOrUpdateUserInvoiceInfoWithEnterpriseInfo()
    ↓
保存开票信息(企业认证信息自动填充)

技术实现要点

1. 接口设计

  • 向后兼容:保留原有的基础方法
  • 功能扩展:新增包含企业认证信息的方法
  • 参数传递:通过方法参数传递跨域数据

2. 错误处理

  • 分层处理:在应用服务层处理跨域错误
  • 错误传播:领域服务层错误向上传播
  • 用户友好:提供清晰的错误信息

3. 性能优化

  • 减少查询:应用服务层缓存企业认证信息
  • 批量操作:支持批量获取和更新
  • 异步处理:非关键路径支持异步处理

代码示例

1. 领域服务实现

// GetUserInvoiceInfoWithEnterpriseInfo 获取用户开票信息(包含企业认证信息)
func (s *UserInvoiceInfoServiceImpl) GetUserInvoiceInfoWithEnterpriseInfo(ctx context.Context, userID string, companyName, taxpayerID string) (*entities.UserInvoiceInfo, error) {
    info, err := s.userInvoiceInfoRepo.FindByUserID(ctx, userID)
    if err != nil {
        return nil, fmt.Errorf("获取用户开票信息失败: %w", err)
    }
    
    // 如果没有找到开票信息记录,创建新的实体
    if info == nil {
        info = &entities.UserInvoiceInfo{
            ID:             uuid.New().String(),
            UserID:         userID,
            CompanyName:    "",
            TaxpayerID:     "",
            BankName:       "",
            BankAccount:    "",
            CompanyAddress: "",
            CompanyPhone:   "",
            ReceivingEmail: "",
        }
    }
    
    // 使用传入的企业认证信息填充公司名称和纳税人识别号
    if companyName != "" {
        info.CompanyName = companyName
    }
    if taxpayerID != "" {
        info.TaxpayerID = taxpayerID
    }
    
    return info, nil
}

2. 应用服务实现

func (s *InvoiceApplicationServiceImpl) UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error {
    // 获取用户企业认证信息
    user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
    if err != nil {
        return fmt.Errorf("获取用户企业认证信息失败: %w", err)
    }

    // 检查用户是否有企业认证信息
    if user.EnterpriseInfo == nil {
        return fmt.Errorf("用户未完成企业认证,无法创建开票信息")
    }

    // 创建开票信息对象
    invoiceInfo := value_objects.NewInvoiceInfo(
        "", // 公司名称将由服务层从企业认证信息中获取
        "", // 纳税人识别号将由服务层从企业认证信息中获取
        req.BankName,
        req.BankAccount,
        req.CompanyAddress,
        req.CompanyPhone,
        req.ReceivingEmail,
    )

    // 使用包含企业认证信息的方法
    _, err = s.userInvoiceInfoService.CreateOrUpdateUserInvoiceInfoWithEnterpriseInfo(
        ctx, 
        userID, 
        invoiceInfo, 
        user.EnterpriseInfo.CompanyName, 
        user.EnterpriseInfo.UnifiedSocialCode,
    )
    return err
}

总结

通过按照DDD规范重新设计我们实现了

  1. 架构规范严格遵循DDD的领域边界和依赖方向
  2. 职责分离:领域服务专注于本领域逻辑,应用服务负责跨域协调
  3. 可维护性:代码结构清晰,易于理解和维护
  4. 可扩展性:新增跨域功能时只需修改应用服务层
  5. 业务逻辑:企业认证信息自动填充功能完整实现

这种设计既满足了业务需求又符合DDD架构规范是一个优秀的架构实现。