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 通信
|
||
- 团队可以专注于特定业务领域
|
||
|
||
这样组织代码后,你的项目会更容易理解、维护和扩展!
|