288 lines
9.7 KiB
Markdown
288 lines
9.7 KiB
Markdown
# DDD规范企业认证信息自动填充实现总结
|
||
|
||
## 概述
|
||
|
||
根据DDD(领域驱动设计)架构规范,重新设计了企业认证信息自动填充功能。在DDD中,跨域操作应该通过应用服务层来协调,而不是在领域服务层直接操作其他领域的仓储。
|
||
|
||
## DDD架构规范
|
||
|
||
### 1. 领域边界原则
|
||
- **领域服务层**:只能操作本领域的仓储和实体
|
||
- **应用服务层**:负责跨域协调,调用不同领域的服务
|
||
- **聚合根**:每个领域有自己的聚合根,不能直接访问其他领域的聚合根
|
||
|
||
### 2. 依赖方向
|
||
```
|
||
应用服务层 → 领域服务层 → 仓储层
|
||
↓
|
||
跨域协调
|
||
```
|
||
|
||
## 重新设计架构
|
||
|
||
### 1. 领域服务层(Finance Domain)
|
||
|
||
#### `UserInvoiceInfoService`接口更新
|
||
```go
|
||
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`更新
|
||
```go
|
||
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
|
||
}
|
||
```
|
||
|
||
#### 跨域协调逻辑
|
||
```go
|
||
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. 依赖注入更新
|
||
|
||
#### 容器配置
|
||
```go
|
||
// 用户聚合服务
|
||
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. 领域服务实现
|
||
```go
|
||
// 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. 应用服务实现
|
||
```go
|
||
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架构规范,是一个优秀的架构实现。 |