399 lines
9.8 KiB
Markdown
399 lines
9.8 KiB
Markdown
|
# Domain 域概念详解
|
|||
|
|
|||
|
## 🤔 什么是 Domain(域)?
|
|||
|
|
|||
|
**Domain(域)** 就像现实世界中的**部门**或**业务范围**。
|
|||
|
|
|||
|
### 📚 现实世界的类比
|
|||
|
|
|||
|
想象一个大公司:
|
|||
|
|
|||
|
```
|
|||
|
阿里巴巴集团
|
|||
|
├── 电商域 (淘宝、天猫)
|
|||
|
├── 支付域 (支付宝)
|
|||
|
├── 云计算域 (阿里云)
|
|||
|
├── 物流域 (菜鸟)
|
|||
|
└── 金融域 (蚂蚁金服)
|
|||
|
```
|
|||
|
|
|||
|
每个域都有:
|
|||
|
|
|||
|
- **专门的团队** - 不同的开发团队
|
|||
|
- **独立的业务** - 各自负责不同的业务功能
|
|||
|
- **清晰的边界** - 知道自己管什么,不管什么
|
|||
|
- **独立运作** - 可以独立决策和发展
|
|||
|
|
|||
|
## 🏗️ 在软件架构中的应用
|
|||
|
|
|||
|
### 传统方式 vs 域驱动方式
|
|||
|
|
|||
|
#### ❌ 传统方式(技术驱动)
|
|||
|
|
|||
|
```
|
|||
|
项目结构:
|
|||
|
├── controllers/ # 所有控制器
|
|||
|
├── services/ # 所有服务
|
|||
|
├── models/ # 所有数据模型
|
|||
|
└── utils/ # 工具类
|
|||
|
|
|||
|
问题:
|
|||
|
- 用户相关、产品相关、支付相关的代码混在一起
|
|||
|
- 不同业务逻辑耦合
|
|||
|
- 团队协作困难
|
|||
|
- 修改一个功能可能影响其他功能
|
|||
|
```
|
|||
|
|
|||
|
#### ✅ 域驱动方式(业务驱动)
|
|||
|
|
|||
|
```
|
|||
|
项目结构:
|
|||
|
├── user-domain/ # 用户域
|
|||
|
│ ├── user/ # 用户管理
|
|||
|
│ ├── auth/ # 认证授权
|
|||
|
│ └── profile/ # 用户资料
|
|||
|
├── product-domain/ # 产品域
|
|||
|
│ ├── catalog/ # 产品目录
|
|||
|
│ ├── inventory/ # 库存管理
|
|||
|
│ └── pricing/ # 价格管理
|
|||
|
├── payment-domain/ # 支付域
|
|||
|
│ ├── wallet/ # 钱包
|
|||
|
│ ├── order/ # 订单
|
|||
|
│ └── billing/ # 计费
|
|||
|
└── notification-domain/ # 通知域
|
|||
|
├── email/ # 邮件通知
|
|||
|
├── sms/ # 短信通知
|
|||
|
└── push/ # 推送通知
|
|||
|
|
|||
|
优势:
|
|||
|
- 按业务功能组织代码
|
|||
|
- 团队可以独立开发不同的域
|
|||
|
- 修改用户功能不会影响支付功能
|
|||
|
- 新人容易理解业务边界
|
|||
|
```
|
|||
|
|
|||
|
## 🎯 你的项目中的 Domain 重构
|
|||
|
|
|||
|
### 当前问题
|
|||
|
|
|||
|
你的项目中有这些编码式命名:
|
|||
|
|
|||
|
```
|
|||
|
IVYZ - 不知道是什么业务
|
|||
|
FLXG - 不知道是什么业务
|
|||
|
QYGL - 不知道是什么业务
|
|||
|
YYSY - 不知道是什么业务
|
|||
|
JRZQ - 不知道是什么业务
|
|||
|
```
|
|||
|
|
|||
|
### 重构后的 Domain 设计
|
|||
|
|
|||
|
```
|
|||
|
用户域 (User Domain)
|
|||
|
├── 用户注册登录
|
|||
|
├── 用户信息管理
|
|||
|
├── 企业认证
|
|||
|
└── 权限管理
|
|||
|
|
|||
|
金融域 (Financial Domain)
|
|||
|
├── 钱包管理
|
|||
|
├── 支付处理
|
|||
|
├── 账单生成
|
|||
|
└── 财务报表
|
|||
|
|
|||
|
数据服务域 (Data Service Domain)
|
|||
|
├── 风险评估服务 (原 FLXG)
|
|||
|
├── 征信查询服务 (原 JRZQ)
|
|||
|
├── 企业信息服务 (原 QYGL)
|
|||
|
├── 数据查询服务 (原 IVYZ)
|
|||
|
└── 应用系统服务 (原 YYSY)
|
|||
|
|
|||
|
产品域 (Product Domain)
|
|||
|
├── 产品目录管理
|
|||
|
├── 产品订阅
|
|||
|
├── 访问控制
|
|||
|
└── 白名单管理
|
|||
|
|
|||
|
平台域 (Platform Domain)
|
|||
|
├── 管理后台
|
|||
|
├── 系统监控
|
|||
|
├── 日志管理
|
|||
|
└── 配置管理
|
|||
|
```
|
|||
|
|
|||
|
## 💻 代码层面的 Domain 实现
|
|||
|
|
|||
|
### 1. Domain 层的职责
|
|||
|
|
|||
|
```go
|
|||
|
// domain/user/entity/user.go
|
|||
|
package entity
|
|||
|
|
|||
|
import (
|
|||
|
"errors"
|
|||
|
"time"
|
|||
|
)
|
|||
|
|
|||
|
// User 用户实体
|
|||
|
type User struct {
|
|||
|
ID int64 `json:"id"`
|
|||
|
Username string `json:"username"`
|
|||
|
Email string `json:"email"`
|
|||
|
Phone string `json:"phone"`
|
|||
|
Status UserStatus `json:"status"`
|
|||
|
CreatedAt time.Time `json:"created_at"`
|
|||
|
UpdatedAt time.Time `json:"updated_at"`
|
|||
|
}
|
|||
|
|
|||
|
type UserStatus int
|
|||
|
|
|||
|
const (
|
|||
|
UserStatusActive UserStatus = 1
|
|||
|
UserStatusInactive UserStatus = 2
|
|||
|
UserStatusBanned UserStatus = 3
|
|||
|
)
|
|||
|
|
|||
|
// 业务规则:用户名必须是3-20个字符
|
|||
|
func (u *User) ValidateUsername() error {
|
|||
|
if len(u.Username) < 3 || len(u.Username) > 20 {
|
|||
|
return errors.New("用户名必须是3-20个字符")
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// 业务规则:检查用户是否可以登录
|
|||
|
func (u *User) CanLogin() bool {
|
|||
|
return u.Status == UserStatusActive
|
|||
|
}
|
|||
|
|
|||
|
// 业务规则:激活用户
|
|||
|
func (u *User) Activate() error {
|
|||
|
if u.Status == UserStatusBanned {
|
|||
|
return errors.New("被封禁的用户不能激活")
|
|||
|
}
|
|||
|
u.Status = UserStatusActive
|
|||
|
u.UpdatedAt = time.Now()
|
|||
|
return nil
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. Domain Service (领域服务)
|
|||
|
|
|||
|
```go
|
|||
|
// domain/user/service/user_service.go
|
|||
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"errors"
|
|||
|
|
|||
|
"your-project/domain/user/entity"
|
|||
|
"your-project/domain/user/repository"
|
|||
|
)
|
|||
|
|
|||
|
// UserDomainService 用户领域服务
|
|||
|
type UserDomainService struct {
|
|||
|
userRepo repository.UserRepository
|
|||
|
}
|
|||
|
|
|||
|
func NewUserDomainService(userRepo repository.UserRepository) *UserDomainService {
|
|||
|
return &UserDomainService{
|
|||
|
userRepo: userRepo,
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 业务规则:检查用户名是否唯一
|
|||
|
func (s *UserDomainService) IsUsernameUnique(ctx context.Context, username string) (bool, error) {
|
|||
|
existingUser, err := s.userRepo.FindByUsername(ctx, username)
|
|||
|
if err != nil {
|
|||
|
return false, err
|
|||
|
}
|
|||
|
return existingUser == nil, nil
|
|||
|
}
|
|||
|
|
|||
|
// 业务规则:用户注册
|
|||
|
func (s *UserDomainService) RegisterUser(ctx context.Context, username, email, phone string) (*entity.User, error) {
|
|||
|
// 1. 检查用户名唯一性
|
|||
|
unique, err := s.IsUsernameUnique(ctx, username)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
if !unique {
|
|||
|
return nil, errors.New("用户名已存在")
|
|||
|
}
|
|||
|
|
|||
|
// 2. 创建用户实体
|
|||
|
user := &entity.User{
|
|||
|
Username: username,
|
|||
|
Email: email,
|
|||
|
Phone: phone,
|
|||
|
Status: entity.UserStatusActive,
|
|||
|
}
|
|||
|
|
|||
|
// 3. 验证业务规则
|
|||
|
if err := user.ValidateUsername(); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
// 4. 保存用户
|
|||
|
return s.userRepo.Save(ctx, user)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. Repository 接口 (仓储模式)
|
|||
|
|
|||
|
```go
|
|||
|
// domain/user/repository/user_repository.go
|
|||
|
package repository
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"your-project/domain/user/entity"
|
|||
|
)
|
|||
|
|
|||
|
// UserRepository 用户仓储接口
|
|||
|
type UserRepository interface {
|
|||
|
Save(ctx context.Context, user *entity.User) (*entity.User, error)
|
|||
|
FindByID(ctx context.Context, id int64) (*entity.User, error)
|
|||
|
FindByUsername(ctx context.Context, username string) (*entity.User, error)
|
|||
|
FindByEmail(ctx context.Context, email string) (*entity.User, error)
|
|||
|
Update(ctx context.Context, user *entity.User) error
|
|||
|
Delete(ctx context.Context, id int64) error
|
|||
|
List(ctx context.Context, offset, limit int) ([]*entity.User, error)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 4. 应用层 (Application Layer)
|
|||
|
|
|||
|
```go
|
|||
|
// application/user/service/user_app_service.go
|
|||
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
|
|||
|
userDomain "your-project/domain/user/service"
|
|||
|
"your-project/application/user/dto"
|
|||
|
)
|
|||
|
|
|||
|
// UserAppService 用户应用服务
|
|||
|
type UserAppService struct {
|
|||
|
userDomainService *userDomain.UserDomainService
|
|||
|
}
|
|||
|
|
|||
|
func NewUserAppService(userDomainService *userDomain.UserDomainService) *UserAppService {
|
|||
|
return &UserAppService{
|
|||
|
userDomainService: userDomainService,
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 用户注册用例
|
|||
|
func (s *UserAppService) RegisterUser(ctx context.Context, req *dto.RegisterUserRequest) (*dto.UserResponse, error) {
|
|||
|
// 1. 调用领域服务
|
|||
|
user, err := s.userDomainService.RegisterUser(ctx, req.Username, req.Email, req.Phone)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
// 2. 转换为 DTO
|
|||
|
return &dto.UserResponse{
|
|||
|
ID: user.ID,
|
|||
|
Username: user.Username,
|
|||
|
Email: user.Email,
|
|||
|
Phone: user.Phone,
|
|||
|
Status: string(user.Status),
|
|||
|
}, nil
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🔄 域与域之间的交互
|
|||
|
|
|||
|
### 事件驱动交互
|
|||
|
|
|||
|
```go
|
|||
|
// 用户注册成功后,通知其他域
|
|||
|
type UserRegisteredEvent struct {
|
|||
|
UserID int64 `json:"user_id"`
|
|||
|
Username string `json:"username"`
|
|||
|
Email string `json:"email"`
|
|||
|
}
|
|||
|
|
|||
|
// 金融域监听用户注册事件,自动创建钱包
|
|||
|
func (h *WalletEventHandler) HandleUserRegistered(event UserRegisteredEvent) error {
|
|||
|
// 为新用户创建钱包
|
|||
|
wallet := &entity.Wallet{
|
|||
|
UserID: event.UserID,
|
|||
|
Balance: 0.0,
|
|||
|
Currency: "CNY",
|
|||
|
}
|
|||
|
return h.walletRepo.Save(context.Background(), wallet)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 📊 Domain 的好处
|
|||
|
|
|||
|
### 1. **团队协作**
|
|||
|
|
|||
|
```
|
|||
|
用户域团队:专注用户相关功能
|
|||
|
├── 张三:负责用户注册登录
|
|||
|
├── 李四:负责用户资料管理
|
|||
|
└── 王五:负责企业认证
|
|||
|
|
|||
|
支付域团队:专注支付相关功能
|
|||
|
├── 赵六:负责钱包功能
|
|||
|
├── 孙七:负责支付流程
|
|||
|
└── 周八:负责账单生成
|
|||
|
```
|
|||
|
|
|||
|
### 2. **独立部署**
|
|||
|
|
|||
|
```bash
|
|||
|
# 可以独立部署不同的域
|
|||
|
kubectl apply -f user-domain-deployment.yaml
|
|||
|
kubectl apply -f payment-domain-deployment.yaml
|
|||
|
kubectl apply -f product-domain-deployment.yaml
|
|||
|
```
|
|||
|
|
|||
|
### 3. **技术选择自由**
|
|||
|
|
|||
|
```
|
|||
|
用户域:使用 PostgreSQL (复杂查询)
|
|||
|
支付域:使用 MySQL (事务性强)
|
|||
|
数据域:使用 ClickHouse (分析查询)
|
|||
|
```
|
|||
|
|
|||
|
### 4. **测试隔离**
|
|||
|
|
|||
|
```go
|
|||
|
// 只测试用户域,不需要启动其他域
|
|||
|
func TestUserDomain(t *testing.T) {
|
|||
|
// 只需要用户域的依赖
|
|||
|
userRepo := mock.NewUserRepository()
|
|||
|
userService := service.NewUserDomainService(userRepo)
|
|||
|
|
|||
|
// 测试用户注册逻辑
|
|||
|
user, err := userService.RegisterUser(ctx, "testuser", "test@example.com", "13800138000")
|
|||
|
assert.NoError(t, err)
|
|||
|
assert.Equal(t, "testuser", user.Username)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🎯 总结
|
|||
|
|
|||
|
**Domain(域)就是按业务功能划分的代码组织方式**:
|
|||
|
|
|||
|
1. **用户域** - 管理用户相关的所有功能
|
|||
|
2. **支付域** - 管理支付相关的所有功能
|
|||
|
3. **产品域** - 管理产品相关的所有功能
|
|||
|
4. **数据域** - 管理数据查询相关的功能
|
|||
|
|
|||
|
**核心思想**:
|
|||
|
|
|||
|
- 按业务划分,不按技术划分
|
|||
|
- 每个域独立开发、测试、部署
|
|||
|
- 域之间通过事件或 API 通信
|
|||
|
- 团队可以专注于特定业务领域
|
|||
|
|
|||
|
这样组织代码后,你的项目会更容易理解、维护和扩展!
|