.. | ||
Base/emitter | ||
Tracker | ||
User | ||
readme.md |
领域层(domain):这一层包含了业务领域的所有规则和逻辑。这里会定义
- 实体(Entity):
- 领域服务(Domain Service
- 领域事件 (Event) : 领域事件是领域模型中发生的重要的业务事件,例如用户登录、订单提交等。它们通常表示了领域模型的状态变化,它通常表示了领域模型的状态变化,但它们并
不知道如何处理这些变化
。它们的职责是通知其他部分系统,某个重要的事情发生了。 - 仓库接口(Repository Interface)
更轻量级的领域设计,可以考虑以下策略:
- 实体(Entity)
- 领域服务(Domain Service)
- 领域事件 (Event)
领域事件(Domain Events)、领域模型(Domain Model)、UI 三者的关系
- UI层接收用户输入:用户通过UI层进行操作,例如点击按钮或填写表单。
- 触发领域事件:根据用户的操作,UI层会触发相应的领域事件。例如,当用户填写表单并点击"登录"按钮时,UI层会触发一个"UserLoggedIn"事件。
- 更新领域模型:领域事件会导致领域模型的状态改变。例如,"UserLoggedIn"事件可能会使User模型的"isLoggedIn"状态变为true。
- UI层反映领域模型的状态:当领域模型的状态改变时,UI层会相应地更新,以反映新的状态。例如,如果User模型的"isLoggedIn"状态变为true,那么UI层可能会显示一个"欢迎回来"的消息,并隐藏"登录"按钮。
这种方式的好处是,它使得UI层和领域逻辑解耦,使得代码更易于理解和维护。当业务逻辑变化时,你只需要修改领域模型和领域事件,而不需要修改UI层。同样,当UI需求变化时,你只需要修改UI层,而不需要修改领域模型和领域事件。
基本的Demo
class User {
id: string;
name: string;
isLoggedIn: boolean = false;
login() {
this.isLoggedIn = true;
}
}
接下来,我们定义用户登录的领域事件。这个事件将表示用户登录的动作:
class UserLoggedIn {
constructor(public userId: string) {}
}
然后,我们需要定义一个领域服务,这个服务将负责处理用户登录的业务逻辑:
class AuthenticationService {
constructor(private userRepository: UserRepository) {}
async login(username: string, password: string) {
// 通常,你需要从后端服务验证用户名和密码,这里为了简单起见,我们假设验证总是成功
const user = await this.userRepository.findByName(username);
user.login();
// 通过发布订阅这种方式通知UI更新
eventBus.emit('UserLoggedIn',new UserLoggedIn(user.id));
}
}
某个方法 Should 充血到领域模型 Or 放到领域服务
-
领域规则:如果方法代表了一个业务规则,那么它应该被放在领域模型中。领域模型负责封装业务规则,保证业务的一致性。例如,User模型可能有一个login方法,因为登录是用户的一个核心业务行为。
-
状态变化:如果方法会改变模型的状态,那么它也应该被放在领域模型中。领域模型的状态应该被封装起来,只能通过模型的方法来改变。这样可以保证状态的一致性。
-
复杂业务逻辑:如果方法包含了复杂的业务逻辑,那么它可能应该被放在一个领域服务中。领域服务是一种特殊的模型,它负责处理那些涉及多个模型的复杂业务逻辑。例如,一个订单服务(OrderService)可能有一个placeOrder方法,因为下订单涉及到用户、商品和订单等多个模型。
mobx与发布订阅
针对 f(领域模型状态)->ui组件,这个通常有如下几种做法:
- mobx: makeAutoObserve(this) + mobx-react-lite: + useDomain(Context)
- 领域服务发布领域事件 + 组件内部订阅最新的领域模型
ui层使用mobx的observer()
来订阅反应模型、事件的更新,相比于发布订阅,省去了在组件内部显式的订阅和处理领域事件
实例化的两种思量
构造函数的参数列表中使用了 public
关键字,这样 TypeScript 会自动为每个参数创建一个与之同名的类属性,并把构造函数收到的参数值赋给这些属性。
缺点是不能直接接受一个对象作为参数
export class UserInfo {
constructor(
public id?: number,
public username?: string,
public isLoggedIn?: boolean,
public phoneNumber?: string | number,
public createTime?: number | string,
public department?: string,
public contactPerson?: string
) {}
}
如果userInfo对象缺少一些UserInfo类有的属性,这些属性将保持其初始值
type UserInfoInterface = {
id?: number;
username?: string;
isLoggedIn?: boolean;
phoneNumber?: string | number;
createTime?: number | string;
department?: string;
contactPerson?: string;
};
export class UserInfo {
id?: number;
username?: string;
isLoggedIn?: boolean;
phoneNumber?: string | number;
createTime?: number | string;
department?: string;
contactPerson?: string;
constructor(userInfo: UserInfoInterface) {
makeAutoObservable(this);
// 使用 Object.assign 来分配属性
Object.assign(this, userInfo);
}
}
makeAutoObservable(this)
- 需要构造属性声明的最后面
- 实例化之后的属性注入,无法被追踪到