-- 知识铺
注意:此内容目前正在审核中,并且随着我不断完善我的理解并收集更多见解,可能会进行更新。请在阅读时考虑这一点,并随时提出您可能有的任何建议或更正。
我们非常感谢您的反馈,这将有助于提高本次讨论的准确性和深度。
介绍
欢迎来到面向初学者的领域驱动设计 (DDD) 系列的第一部分。在这篇文章中,我们将探讨端口和适配器的概念,并深入研究事件驱动架构 (EDA),重点关注 Pub/Sub 模型。
一些术语(考虑图书馆管理系统示例)
领域:管理图书馆的运营。
子域:目录管理、会员管理、借阅管理。
实体:图书(标题、作者、ISBN、状态)、会员(ID、姓名、会员日期)。
聚合:图书馆(书籍和成员)。
领域服务:BorrowingService(处理借阅和归还图书)。
值对象:地址(街道、城市、邮政编码)。
领域驱动设计 (DDD) 中的适配器
定义:适配器是端口的具体实现。它们将应用程序连接到数据库、Web 服务或消息传递系统等外部系统。
关键点
-
端口和适配器模式:
-
端口:定义将核心应用程序逻辑与外部系统解耦的接口。
-
适配器:实现这些接口以连接到特定的外部系统。
-
-
关注点分离:
-
核心逻辑:保持独立于外部系统,纯粹关注业务规则。
-
适配器:处理与外部系统交互的细节,使得更容易替换或修改这些交互而不影响核心逻辑。
-
例子
为了说明这个概念,让我们考虑一个需要从远程 API 获取用户数据并将其保存到数据库的应用程序。以下是我们如何为此场景定义和实现端口和适配器:
定义端口(接口)
用户存储库:
interface UserRepository {
saveUser(user: User): Promise<void>;
}
=====还有=====
用户API客户端:
interface UserApiClient {
fetchUser(userId: string): Promise<User>;
}
定义适配器(具体实现)
数据库适配器:
class DatabaseUserRepository implements UserRepository {
async saveUser(user: User): Promise<void> {
// Logic to save user to the database
console.log(`Saving user ${user.id} to the database`);
}
}
API适配器:
class RemoteUserApiClient implements UserApiClient {
async fetchUser(userId: string): Promise<User> {
// Logic to fetch user from the remote API
console.log(`Fetching user ${userId} from remote API`);
return { id: userId, name: 'John Doe' }; // Mocked user data
}
}
使用端口的应用程序服务
用户服务:
class UserService {
private readonly userRepository: UserRepository;
private readonly userApiClient: UserApiClient;
constructor(userRepository: UserRepository, userApiClient: UserApiClient) {
this.userRepository = userRepository;
this.userApiClient = userApiClient;
}
async fetchAndSaveUser(userId: string): Promise<void> {
const user = await this.userApiClient.fetchUser(userId);
await this.userRepository.saveUser(user);
}
}
要点:
适配器在核心应用程序逻辑和外部系统之间提供了一座桥梁,确保了关注点的清晰分离。这使得系统在适应变化或新的集成方面更加模块化、可维护和灵活。
[
事件驱动架构(Pub/Sub 模型)
定义:事件驱动架构(EDA)是一种设计模式,其中程序的流程由事件决定。事件可以定义为状态的重大变化。 Pub/Sub(发布/订阅)模型是 EDA 的常见实现,其中:
-
发布者:生成并发送事件。
-
订阅者:监听事件并对事件做出反应。
关键概念
-
事件:更改或重大操作的通知(例如“已下订单”、“用户注册”)。
-
发布者:发生某些操作时发出事件的组件。
-
订阅者:响应特定事件、执行任务或触发其他进程的组件。
-
事件总线:将事件从发布者路由到订阅者的机制。
简单说明和示例
场景:用户在电子商务网站下订单。
-
订单服务:下订单时发布事件。
-
库存服务:订阅下单事件以更新库存。
-
通知服务:订阅下订单事件以发送确认电子邮件。
插图:
[
带有硬编码示例的事件驱动架构(Pub/Sub 模型)
概念:
-
事件:更改或重大操作的通知 (
e.g., "BookBorrowed", "BookReturned", "BookAdded").
-
发布者:发生某些操作时发出事件的组件。
-
订阅者:响应特定事件、执行任务或触发其他进程的组件。
-
事件总线:将事件从发布者路由到订阅者的机制。
组件和工作流程
事件定义:定义事件的结构。事件处理程序:定义事件发生时要执行的操作。事件发布者:负责发布事件的组件。主应用程序:使用事件发布者发出事件的组件。
分步示例
1. 定义事件处理程序:(订阅者):
-
定义事件发生时要采取的具体操作。这些在
eventHandlers.js.
中定义 -
例如,当发布
BookBorrowedEvent
时,处理程序会更新图书的状态、发送通知并记录事件。
eventHandlers.js:
const eventHandlers = {
'BookBorrowedEvent': [
(bookData) => {
// Update the book's status in the database
console.log(`Updating status to borrowed for book ${bookData.id}`);
// Imagine this function updates the status in the database
},
(bookData) => {
// Send a notification to the user who borrowed the book
console.log(`Sending notification to ${bookData.user} for book ${bookData.title}`);
// Imagine this function sends a notification
},
(bookData) => {
// Log the book borrowing event
console.log(`Logging borrow event for book ${bookData.id}`);
// Imagine this function logs the event
}
],
'BookReturnedEvent': [
(bookData) => {
// Update the book's status in the database
console.log(`Updating status to available for book ${bookData.id}`);
// Imagine this function updates the status in the database
},
(bookData) => {
// Send a notification to the librarian
console.log(`Sending return notification for book ${bookData.title}`);
// Imagine this function sends a notification
}
],
'BookAddedEvent': [
(bookData) => {
// Add the new book to the catalog
console.log(`Adding book ${bookData.title} to the catalog`);
// Imagine this function adds the book to the catalog
},
(bookData) => {
// Update the total book count
console.log(`Updating total book count for the library`);
// Imagine this function updates the total book count
},
(bookData) => {
// Log the book addition event
console.log(`Logging addition of book ${bookData.title}`);
// Imagine this function logs the event
}
]
};
export default eventHandlers;
2. 定义事件:
-
代表系统中的重大操作或更改。
-
示例包括
BookBorrowedEvent
、BookReturnedEvent
和BookAddedEvent
。
events.js:
class BookBorrowedEvent {
constructor(bookData) {
this.bookData = bookData;
}
}
class BookReturnedEvent {
constructor(bookData) {
this.bookData = bookData;
}
}
class BookAddedEvent {
constructor(bookData) {
this.bookData = bookData;
}
}
export { BookBorrowedEvent, BookReturnedEvent, BookAddedEvent };
3.定义事件发布者:
-
EventPublisher
类将事件路由到其相应的处理程序。 -
发布事件时,
publish
方法会查找事件类型并使用事件数据调用每个已注册的处理程序。
eventPublisher.js:
import eventHandlers from './eventHandlers.js';
class EventPublisher {
publish(event) {
const eventName = event.constructor.name;
if (eventHandlers[eventName]) {
for (const handler of eventHandlers[eventName]) {
handler(event.bookData);
}
}
}
}
export default EventPublisher;
四、主要应用
应用程序.js:
[
要点说明:在事件驱动架构中,事件可以被视为端口,事件处理程序和发布者可以被视为适配器。这种关系确保核心应用程序与外部系统保持解耦,从而促进模块化和灵活性。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240627/DDD-%E5%88%9D%E5%AD%A6%E8%80%85%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com