monorepo-microservice-rbac/apps/aorta/core/domain
2023-08-27 14:37:59 +08:00
..
Base/emitter first commit 2023-08-27 14:37:59 +08:00
Tracker first commit 2023-08-27 14:37:59 +08:00
User first commit 2023-08-27 14:37:59 +08:00
readme.md first commit 2023-08-27 14:37:59 +08:00

领域层domain这一层包含了业务领域的所有规则和逻辑。这里会定义

  • 实体Entity:
  • 领域服务Domain Service
  • 领域事件 (Event) : 领域事件是领域模型中发生的重要的业务事件,例如用户登录、订单提交等。它们通常表示了领域模型的状态变化,它通常表示了领域模型的状态变化,但它们并不知道如何处理这些变化。它们的职责是通知其他部分系统,某个重要的事情发生了。
  • 仓库接口Repository Interface

更轻量级的领域设计,可以考虑以下策略:

  • 实体Entity
  • 领域服务Domain Service
  • 领域事件 (Event)

领域事件Domain Events、领域模型Domain Model、UI 三者的关系

  1. UI层接收用户输入用户通过UI层进行操作例如点击按钮或填写表单。
  2. 触发领域事件根据用户的操作UI层会触发相应的领域事件。例如当用户填写表单并点击"登录"按钮时UI层会触发一个"UserLoggedIn"事件。
  3. 更新领域模型:领域事件会导致领域模型的状态改变。例如,"UserLoggedIn"事件可能会使User模型的"isLoggedIn"状态变为true。
  4. 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 放到领域服务

  1. 领域规则如果方法代表了一个业务规则那么它应该被放在领域模型中。领域模型负责封装业务规则保证业务的一致性。例如User模型可能有一个login方法因为登录是用户的一个核心业务行为。

  2. 状态变化:如果方法会改变模型的状态,那么它也应该被放在领域模型中。领域模型的状态应该被封装起来,只能通过模型的方法来改变。这样可以保证状态的一致性。

  3. 复杂业务逻辑如果方法包含了复杂的业务逻辑那么它可能应该被放在一个领域服务中。领域服务是一种特殊的模型它负责处理那些涉及多个模型的复杂业务逻辑。例如一个订单服务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)

  1. 需要构造属性声明的最后面
  2. 实例化之后的属性注入,无法被追踪到