From ba492827fe6f7657620ab361b9bb9b3283c11f60 Mon Sep 17 00:00:00 2001 From: wehub-resource-sync Date: Mon, 13 Jul 2026 10:21:40 +0000 Subject: [PATCH] docs: make Chinese README the default --- README.md | 459 ++++++++++++++++++++++++------------------------------ 1 file changed, 203 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index 111b1ae..d9ed61a 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,119 @@ + +> [!NOTE] +> 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。 +> [English](./README.en.md) · [原始项目](https://github.com/ddd-by-examples/library) · [上游 README](https://github.com/ddd-by-examples/library/blob/HEAD/README.md) +> 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。 + [![CircleCI](https://circleci.com/gh/ddd-by-examples/library.svg?style=svg)](https://circleci.com/gh/ddd-by-examples/library) [![Code Coverage](https://codecov.io/gh/ddd-by-examples/library/branch/master/graph/badge.svg)](https://codecov.io/gh/ddd-by-examples/library) -# Table of contents +# 目录 -1. [About](#about) -2. [Domain description](#domain-description) -3. [General assumptions](#general-assumptions) - 3.1 [Process discovery](#process-discovery) - 3.2 [Project structure and architecture](#project-structure-and-architecture) - 3.3 [Aggregates](#aggregates) - 3.4 [Events](#events) - 3.4.1 [Events in Repositories](#events-in-repositories) +1. [关于](#about) +2. [领域描述](#domain-description) +3. [总体假设](#general-assumptions) + 3.1 [流程发现](#process-discovery) + 3.2 [项目结构与架构](#project-structure-and-architecture) + 3.3 [聚合(Aggregates)](#aggregates) + 3.4 [事件(Events)](#events) + 3.4.1 [仓储中的事件](#events-in-repositories) 3.5 [ArchUnit](#archunit) - 3.6 [Functional thinking](#functional-thinking) - 3.7 [No ORM](#no-orm) - 3.8 [Architecture-code gap](#architecture-code-gap) - 3.9 [Model-code gap](#model-code-gap) + 3.6 [函数式思维](#functional-thinking) + 3.7 [不使用 ORM](#no-orm) + 3.8 [架构与代码的差距](#architecture-code-gap) + 3.9 [模型与代码的差距](#model-code-gap) 3.10 [Spring](#spring) - 3.11 [Tests](#tests) -4. [How to contribute](#how-to-contribute) -5. [References](#references) + 3.11 [测试](#tests) +4. [如何贡献](#how-to-contribute) +5. [参考资料](#references) ## About -This is a project of a library, driven by real [business requirements](#domain-description). -We use techniques strongly connected with Domain Driven Design, Behavior-Driven Development, -Event Storming, User Story Mapping. +这是一个图书馆(library)项目,由真实的[业务需求](#domain-description)驱动。 +我们采用与领域驱动设计(Domain Driven Design)、行为驱动开发(Behavior-Driven Development)、 +事件风暴(Event Storming)、用户故事地图(User Story Mapping)紧密相关的技术。 ## Domain description -A public library allows patrons to place books on hold at its various library branches. -Available books can be placed on hold only by one patron at any given point in time. -Books are either circulating or restricted, and can have retrieval or usage fees. -A restricted book can only be held by a researcher patron. A regular patron is limited -to five holds at any given moment, while a researcher patron is allowed an unlimited number -of holds. An open-ended book hold is active until the patron checks out the book, at which time it -is completed. A closed-ended book hold that is not completed within a fixed number of -days after it was requested will expire. This check is done at the beginning of a day by -taking a look at daily sheet with expiring holds. Only a researcher patron can request -an open-ended hold duration. Any patron with more than two overdue checkouts at a library -branch will get a rejection if trying a hold at that same library branch. A book can be -checked out for up to 60 days. Check for overdue checkouts is done by taking a look at -daily sheet with overdue checkouts. Patron interacts with his/her current holds, checkouts, etc. -by taking a look at patron profile. Patron profile looks like a daily sheet, but the -information there is limited to one patron and is not necessarily daily. Currently a -patron can see current holds (not canceled nor expired) and current checkouts (including overdue). -Also, he/she is able to hold a book and cancel a hold. +公共图书馆允许读者在其各分馆为图书办理预约(hold)。 +在任意时刻,可预约的图书只能被一位读者预约。 +图书分为流通型(circulating)或限制型(restricted),并可能收取取书费或使用费。 +限制型图书只能由研究员读者(researcher patron)预约。普通读者在任意时刻最多只能有五笔预约, +而研究员读者可拥有不限数量的预约。开放式预约(open-ended book hold)在读者借出图书之前一直有效, +借出时即告完成。若在申请后固定天数内未完成的封闭式预约(closed-ended book hold)将过期。 +该检查在每天开始时进行,方法是查看包含即将过期预约的日报(daily sheet)。 +只有研究员读者可以申请开放式预约期限。若某读者在某分馆有超过两笔逾期借出(overdue checkouts), +则在该分馆尝试预约时会被拒绝。图书最多可借出 60 天。逾期借出的检查通过查看 +包含逾期借出的日报来完成。读者通过查看读者档案(patron profile)来了解自己的当前预约、借出等情况。 +读者档案类似日报,但信息仅限于一位读者,且不一定是按日汇总的。目前读者可以查看当前预约 +(未取消且未过期)和当前借出(含逾期)。此外,他/她还可以预约图书和取消预约。 -How actually a patron knows which books are there to lend? Library has its catalogue of -books where books are added together with their specific instances. A specific book -instance of a book can be added only if there is book with matching ISBN already in -the catalogue. Book must have non-empty title and price. At the time of adding an instance -we decide whether it will be Circulating or Restricted. This enables -us to have book with same ISBN as circulated and restricted at the same time (for instance, -there is a book signed by the author that we want to keep as Restricted) +读者实际上如何知道有哪些书可借?图书馆拥有图书目录(catalogue), +图书及其具体实例会一并加入其中。只有当目录中已存在 ISBN 匹配的图书时, +才能添加该图书的具体实例。图书必须具有非空标题和价格。在添加实例时, +我们决定它是流通型(Circulating)还是限制型(Restricted)。这使我们可以 +让同一 ISBN 的图书同时存在流通型和限制型(例如,有一本作者签名的书,我们希望将其保留为限制型)。 ## General assumptions ### Process discovery -The first thing we started with was domain exploration with the help of Big Picture EventStorming. -The description you found in the previous chapter, landed on our virtual wall: +我们首先借助 Big Picture EventStorming 进行领域探索。 +你在上一章看到的描述,被贴在了我们的虚拟墙上: ![Event Storming Domain description](docs/images/eventstorming-domain-desc.png) -The EventStorming session led us to numerous discoveries, modeled with the sticky notes: +EventStorming 会议引导我们做出了诸多发现,并用便利贴进行建模: ![Event Storming Big Picture](docs/images/eventstorming-big-picture.jpg) -During the session we discovered following definitions: +在会议期间,我们发现了以下定义: ![Event Storming Definitions](docs/images/eventstorming-definitions.png) -This made us think of real life scenarios that might happen. We discovered them described with the help of -the **Example mapping**: +这促使我们思考现实生活中可能发生的情景。我们借助 +**Example mapping** 对它们进行了描述: ![Example mapping](docs/images/example-mapping.png) -This in turn became the base for our *Design Level* sessions, where we analyzed each example: +这进而成为我们 *Design Level* 会议的基础,我们在其中分析了每个示例: ![Example mapping](docs/images/eventstorming-design-level.jpg) -Please follow the links below to get more details on each of the mentioned steps: +请通过以下链接了解上述各步骤的更多细节: - [Big Picture EventStorming](./docs/big-picture.md) - [Example Mapping](docs/example-mapping.md) - [Design Level EventStorming](docs/design-level.md) ### Project structure and architecture -At the very beginning, not to overcomplicate the project, we decided to assign each bounded context -to a separate package, which means that the system is a modular monolith. There are no obstacles, though, -to put contexts into maven modules or finally into microservices. +在项目伊始,为避免过度复杂化,我们决定将每个限界上下文(bounded context) +分配到独立的包中,这意味着系统是一个模块化单体(modular monolith)。不过, +将上下文拆分为 maven 模块乃至最终拆分为微服务,并无障碍。 -Bounded contexts should (amongst others) introduce autonomy in the sense of architecture. Thus, each module -encapsulating the context has its own local architecture aligned to problem complexity. -In the case of a context, where we identified true business logic (**lending**) we introduced a domain model -that is a simplified (for the purpose of the project) abstraction of the reality and utilized -hexagonal architecture. In the case of a context, that during Event Storming turned out to lack any complex -domain logic, we applied CRUD-like local architecture. +限界上下文应当(除其他外)在架构意义上引入自治。因此,封装各上下文的模块 +拥有与其问题复杂度相匹配的本地架构。对于识别出真正业务逻辑(**lending**)的上下文, +我们引入了领域模型——这是对现实的一种简化(为项目目的而简化)抽象,并采用了 +六边形架构(hexagonal architecture)。对于在 Event Storming 中证明缺乏复杂 +领域逻辑的上下文,我们采用了类 CRUD 的本地架构。 ![Architecture](docs/images/architecture-big-picture.png) -If we are talking about hexagonal architecture, it lets us separate domain and application logic from -frameworks (and infrastructure). What do we gain with this approach? Firstly, we can unit test most important -part of the application - **business logic** - usually without the need to stub any dependency. -Secondly, we create ourselves an opportunity to adjust infrastructure layer without the worry of -breaking the core functionality. In the infrastructure layer we intensively use Spring Framework -as probably the most mature and powerful application framework with an incredible test support. -More information about how we use Spring you will find [here](#spring). +若谈及六边形架构,它使我们能够将领域逻辑和应用逻辑与 +框架(及基础设施)分离。采用这种方式我们能获得什么?首先,我们可以对应用程序最重要的部分——**业务逻辑**——进行单元测试, +通常无需 stub 任何依赖。其次,我们为自己创造了调整基础设施层的机会,而无需担心 +破坏核心功能。在基础设施层中,我们大量使用 Spring Framework, +它可能是目前最成熟、最强大且具备出色测试支持的应用框架。 +关于我们如何使用 Spring 的更多信息,请见[此处](#spring)。 -As we already mentioned, the architecture was driven by Event Storming sessions. Apart from identifying -contexts and their complexity, we could also make a decision that we separate read and write models (CQRS). -As an example you can have a look at **Patron Profiles** and *Daily Sheets*. +如前所述,架构由 Event Storming 会议驱动。除识别上下文及其复杂度外, +我们还可以决定分离读写模型(CQRS)。例如,可以查看 **Patron Profiles** 和 *Daily Sheets*。 ### Aggregates -Aggregates discovered during Event Storming sessions communicate with each other with events. There is -a contention, though, should they be consistent immediately or eventually? As aggregates in general -determine business boundaries, eventual consistency sounds like a better choice, but choices in software -are never costless. Providing eventual consistency requires some infrastructural tools, like message broker -or event store. That's why we could (and did) start with immediate consistency. +在 Event Storming 会议中发现的聚合(Aggregates)通过事件相互通信。不过存在争议: +它们应当立即保持一致,还是最终保持一致?由于聚合总体上界定了业务边界, +最终一致性(eventual consistency)听起来是更好的选择,但软件中的选择从无免费午餐。 +提供最终一致性需要一些基础设施工具,例如消息代理(message broker) +或事件存储(event store)。因此我们可以(也确实)从立即一致性开始。 -> Good architecture is the one which postpones all important decisions +> 好的架构会推迟所有重要决策 -... that's why we made it easy to change the consistency model, providing tests for each option, including -basic implementations based on **DomainEvents** interface, which can be adjusted to our needs and -toolset in future. Let's have a look at following examples: +……正因如此,我们使一致性模型易于变更,并为每种选项提供测试,包括基于 **DomainEvents** 接口的基础实现, +未来可根据我们的需求和工具集进行调整。请看以下示例: -* Immediate consistency +* 立即一致性(Immediate consistency) ```groovy def 'should synchronize Patron, Book and DailySheet with events'() { given: @@ -146,9 +141,9 @@ toolset in future. Let's have a look at following examples: .get("COUNT(*)") == 1 } ``` - _Please note that here we are just reading from database right after events are being published_ - - Simple implementation of the event bus is based on Spring application events: + _请注意,此处我们是在事件发布后立即从数据库读取_ + +事件总线的简单实现基于 Spring 应用事件(Spring application events): ```java @AllArgsConstructor public class JustForwardDomainEventPublisher implements DomainEvents { @@ -162,7 +157,7 @@ toolset in future. Let's have a look at following examples: } ``` -* Eventual consistency +* 最终一致性(Eventual consistency) ```groovy def 'should synchronize Patron, Book and DailySheet with events'() { given: @@ -191,10 +186,10 @@ toolset in future. Let's have a look at following examples: } } ``` - _Please note that the test looks exactly the same as previous one, but now we utilized Groovy's - **PollingConditions** to perform asynchronous functionality tests_ + _请注意,该测试与之前的测试看起来完全相同,但现在我们利用了 Groovy 的 + **PollingConditions** 来执行异步功能测试_ - Sample implementation of event bus is following: + 事件总线的示例实现如下: ```java @AllArgsConstructor @@ -218,26 +213,15 @@ toolset in future. Let's have a look at following examples: } ``` -To clarify, we should always aim for aggregates that can handle a business operation atomically -(transactionally if you like), so each aggregate should be as independent and decoupled from other -aggregates as possible. Thus, eventual consistency is promoted. As we already mentioned, it comes -with some tradeoffs, so from the pragmatic point of view immediate consistency is also a choice. -You might ask yourself a question now: _What if I don't have any events yet?_. Well, a pragmatic -approach would be to encapsulate the communication between aggregates in a _Service-like_ class, -where you could call proper aggregates line by line explicitly. +需要澄清的是,我们应始终追求能够原子性地(若你愿意,也可以说是事务性地)处理业务操作的聚合,因此每个聚合应尽可能独立,并与其他聚合解耦。由此,最终一致性(eventual consistency)受到推崇。如前所述,这会带来一些权衡;从务实角度看,即时一致性(immediate consistency)同样是一种选择。 +你现在可能会问自己一个问题:_如果我还没有任何事件呢?_ 那么,一种务实的方法是将聚合之间的通信封装在一个类似 _Service_ 的类中,在其中可以按行显式调用相应的聚合。 ### Events -Talking about inter-aggregate communication, we must remember that events reduce coupling, but don't remove -it completely. Thus, it is very vital to share(publish) only those events, that are necessary for other -aggregates to exist and function. Otherwise there is a threat that the level of coupling will increase -introducing **feature envy**, because other aggregates might start using those events to perform actions -they are not supposed to perform. A solution to this problem could be the distinction of domain events -and integration events, which will be described here soon. +谈到聚合间通信,我们必须记住:事件能降低耦合,但并不能完全消除耦合。因此,只共享(发布)其他聚合存在和运行所必需的事件,这一点至关重要。否则,耦合程度可能会上升,并引入 **feature envy(特性依恋)**,因为其他聚合可能开始利用这些事件去执行本不该由它们执行的操作。解决该问题的一种方案是区分领域事件(domain events)与集成事件(integration events),相关内容将很快在此说明。 ### Events in Repositories -Repositories are one of the most popular design pattern. They abstract our domain model from data layer. -In other words, they deal with state. That said, a common use-case is when we pass a new state to our repository, -so that it gets persisted. It may look like so: +仓储(Repository)是最常见的设计模式之一。它将领域模型与数据层解耦。 +换言之,它处理的是状态。也就是说,常见用例是我们将新状态传给仓储,以便持久化。可能看起来像这样: ```java public class BusinessService { @@ -252,12 +236,12 @@ public class BusinessService { } ``` -Conceptually, between 1st and 3rd line of that business method we change state of our Patron from A to B. -This change might be calculated by dirty checking or we might just override entire Patron state in the database. -Third option is _Let's make implicit explicit_ and actually call this state change A->B an **event**. -After all, event-driven architecture is all about promoting state changes as domain events. +从概念上讲,在该业务方法第 1 行与第 3 行之间,我们将 Patron 的状态从 A 变更为 B。 +这一变更可能通过脏检查(dirty checking)计算得出,也可能只是在数据库中整体覆盖 Patron 状态。 +第三种选择是 _让隐式变为显式_,并将这次 A->B 的状态变更真正称为一个 **event(事件)**。 +毕竟,事件驱动架构的核心,就是推动将状态变更提升为领域事件(domain events)。 -Thanks to this our domain model may become immutable and just return events as results of invoking a command like so: +得益于此,我们的领域模型可以变为不可变的,并在调用命令后仅返回事件,例如: ```java public BookPlacedOnHold placeOnHold(AvailableBook book) { @@ -265,7 +249,7 @@ public BookPlacedOnHold placeOnHold(AvailableBook book) { } ``` -And our repository might operate directly on events like so: +而我们的仓储可以直接基于事件进行操作,例如: ```java public interface PatronRepository { @@ -274,16 +258,9 @@ public interface PatronRepository { ``` ### ArchUnit +成功项目的主要组成部分之一,是能够引导团队朝正确方向前进的技术领导力。尽管如此,仍有一些工具可以帮助团队保持代码整洁、守护架构,使项目不会沦为 Big Ball of Mud(大泥球),从而令开发与维护都更加愉快。我们提出的第一种选择是 [ArchUnit](https://www.archunit.org/))——一个 Java 架构测试工具。ArchUnit 允许你为架构编写单元测试,从而使其始终与最初愿景保持一致。Maven 模块也可以作为替代方案,但此处我们聚焦前者。 -One of the main components of a successful project is technical leadership that lets the team go in the right -direction. Nevertheless, there are tools that can support teams in keeping the code clean and protect the -architecture, so that the project won't become a Big Ball of Mud, and thus will be pleasant to develop and -to maintain. The first option, the one we proposed, is [ArchUnit](https://www.archunit.org/) - a Java architecture -test tool. ArchUnit lets you write unit tests of your architecture, so that it is always consistent with initial -vision. Maven modules could be an alternative as well, but let's focus on the former. - -In terms of hexagonal architecture, it is essential to ensure, that we do not mix different levels of -abstraction (hexagon levels): +就六边形架构(hexagonal architecture)而言,确保我们不混用不同抽象层级(hexagon levels)至关重要: ```java @ArchTest public static final ArchRule model_should_not_depend_on_infrastructure = @@ -294,7 +271,7 @@ public static final ArchRule model_should_not_depend_on_infrastructure = .dependOnClassesThat() .resideInAPackage("..infrastructure.."); ``` -and that frameworks do not affect the domain model +并确保框架不会影响领域模型 ```java @ArchTest public static final ArchRule model_should_not_depend_on_spring = @@ -307,24 +284,18 @@ public static final ArchRule model_should_not_depend_on_spring = ``` ### Functional thinking -When you look at the code you might find a scent of functional programming. Although we do not follow -a _clean_ FP, we try to think of business processes as pipelines or workflows, utilizing functional style through -following concepts. +查看代码时,你可能会嗅到一丝函数式编程(functional programming)的气息。尽管我们并未遵循 _纯粹的_ FP,但我们尝试将业务流程视为流水线或工作流,并通过以下概念以函数式风格加以运用。 -_Please note that this is not a reference project for FP._ +_请注意,本项目并非 FP 的参考项目。_ #### Immutable objects -Each class that represents a business concept is immutable, thanks to which we: -* provide full encapsulation and objects' states protection, -* secure objects for multithreaded access, -* control all side effects much clearer. +每个表示业务概念的类都是不可变的,由此我们可以: +* 提供完整封装并保护对象状态, +* 保障对象在多线程访问下的安全性, +* 更清晰地控制所有副作用。 #### Pure functions -We model domain operations, discovered in Design Level Event Storming, as pure functions, and declare them in -both domain and application layers in the form of Java's functional interfaces. Their implementations are placed -in infrastructure layer as ordinary methods with side effects. Thanks to this approach we can follow the abstraction -of ubiquitous language explicitly, and keep this abstraction implementation-agnostic. As an example, you could have -a look at `FindAvailableBook` interface and its implementation: +我们将设计层事件风暴(Design Level Event Storming)中发现的领域操作建模为纯函数(pure functions),并在领域层与应用层中以 Java 函数式接口的形式声明它们。其实现则作为带有副作用的普通方法放在基础设施层。借助这一做法,我们可以显式遵循通用语言(ubiquitous language)的抽象,并保持该抽象与具体实现无关。例如,你可以查看 `FindAvailableBook` 接口及其实现: ```java @FunctionalInterface @@ -361,14 +332,10 @@ class BookDatabaseRepository implements FindAvailableBook { } } ``` - -#### Type system -_Type system - like_ modelling - we modelled each domain object's state discovered during EventStorming as separate -classes: `AvailableBook`, `BookOnHold`, `CheckedOutBook`. With this approach we provide much clearer abstraction than -having a single `Book` class with an enum-based state management. Moving the logic to these specific classes brings -Single Responsibility Principle to a different level. Moreover, instead of checking invariants in every business method -we leave the role to the compiler. As an example, please consider following scenario: _you can place on hold only a book -that is currently available_. We could have done it in a following way: + +#### 类型系统 +_类型系统(type system)——与建模类似——_ 我们将 EventStorming 过程中发现的每个领域对象状态建模为独立的 +类:`AvailableBook`、`BookOnHold`、`CheckedOutBook`。采用这种方式,我们比使用单一 `Book` 类配合基于枚举的状态管理提供了更清晰的抽象。将逻辑迁移到这些特定类中,把单一职责原则(Single Responsibility Principle)提升到了新的层次。此外,我们不再在每个业务方法中检查不变量,而是将这一职责交给编译器。例如,请考虑以下场景:_只有当前可借阅的图书才能被预约(place on hold)_。我们可以这样实现: ```java public Either placeOnHold(Book book) { if (book.status == AVAILABLE) { @@ -376,38 +343,31 @@ public Either placeOnHold(Book book) { } } ``` -but we use the _type system_ and declare method of following signature +但我们使用_类型系统_,并声明具有如下签名的方法 ```java public Either placeOnHold(AvailableBook book) { ... } ``` -The more errors we discover at compile time the better. +在编译期发现的错误越多越好。 -Yet another advantage of applying such type system is that we can represent business flows and state transitions -with functions much easier. As an example, following functions: +应用此类类型系统的另一项优势是,我们能够用函数更轻松地表达业务流程与状态转换。例如,以下函数: ``` placeOnHold: AvailableBook -> BookHoldFailed | BookPlacedOnHold cancelHold: BookOnHold -> BookHoldCancelingFailed | BookHoldCanceled ``` -are much more concise and descriptive than these: +比下面这些更加简洁且富有表达力: ``` placeOnHold: Book -> BookHoldFailed | BookPlacedOnHold cancelHold: Book -> BookHoldCancelingFailed | BookHoldCanceled ``` -as here we have a lot of constraints hidden within function implementations. +因为在后者中,许多约束都隐藏在函数实现内部。 -Moreover if you think of your domain as a set of operations (functions) that are being executed on business objects -(aggregates) you don't think of any execution model (like async processing). It is fine, because you don't have to. -Domain functions are free from I/O operations, async, and other side-effects-prone things, which are put into the -infrastructure layer. Thanks to this, we can easily test them without mocking mentioned parts. +此外,若将领域视为在一组业务对象(聚合,aggregates)上执行的一组操作(函数),你就不必考虑任何执行模型(例如异步处理)。这完全合理,因为你无需考虑。领域函数不受 I/O 操作、异步以及其他易产生副作用的操作的影响,这些都被放在基础设施层。得益于此,我们可以在不进行 mock 的情况下轻松测试它们。 #### Monads -Business methods might have different results. One might return a value or a `null`, throw an exception when something -unexpected happens or just return different objects under different circumstances. All those situations are typical -to object-oriented languages like Java, but do not fit into functional style. We are dealing with this issues -with monads (monadic containers provided by [Vavr](https://www.vavr.io)): -* When a method returns optional value, we use the `Option` monad: +业务方法可能产生不同的结果。一种可能返回值或 `null`,在发生意外时抛出异常,或在不同情况下返回不同对象。这些情况在 Java 等面向对象语言中很常见,却不符合函数式风格。我们通过 monad(由 [Vavr](https://www.vavr.io)): 提供的单子容器)来解决这些问题 +* 当方法返回可选值时,我们使用 `Option` monad: ```java Option findBy(BookId bookId) { @@ -415,7 +375,7 @@ with monads (monadic containers provided by [Vavr](https://www.vavr.io)): } ``` -* When a method might return one of two possible values, we use the `Either` monad: +* 当方法可能返回两种可能值之一时,我们使用 `Either` monad: ```java Either placeOnHold(AvailableBook book) { @@ -423,7 +383,7 @@ with monads (monadic containers provided by [Vavr](https://www.vavr.io)): } ``` -* When an exception might occur, we use `Try` monad: +* 当可能发生异常时,我们使用 `Try` monad: ```java Try placeOnHold(@NonNull PlaceOnHoldCommand command) { @@ -431,14 +391,10 @@ with monads (monadic containers provided by [Vavr](https://www.vavr.io)): } ``` -Thanks to this, we can follow the functional programming style, but we also enrich our domain language and -make our code much more readable for the clients. +得益于此,我们可以遵循函数式编程风格,同时丰富领域语言,使代码对调用方而言更具可读性。 -#### Pattern Matching -Depending on a type of a given book object we often need to perform different actions. Series of if/else or switch/case statements -could be a choice, but it is the pattern matching that provides the most conciseness and flexibility. With the code -like below we can check numerous patterns against objects and access their constituents, so our code has a minimal dose -of language-construct noise: +#### 模式匹配(Pattern Matching) +根据给定图书对象的类型,我们经常需要执行不同的操作。一连串 if/else 或 switch/case 语句固然可行,但模式匹配能提供最佳的简洁性与灵活性。借助如下代码,我们可以针对对象检查多种模式并访问其组成部分,从而使代码中的语言构造噪声降到最低: ```java private Book handleBookPlacedOnHold(Book book, BookPlacedOnHold bookPlacedOnHold) { return API.Match(book).of( @@ -449,18 +405,17 @@ private Book handleBookPlacedOnHold(Book book, BookPlacedOnHold bookPlacedOnHold } ``` -### (No) ORM -If you run `mvn dependency:tree` you won't find any JPA implementation. Although we think that ORM solutions (like Hibernate) -are very powerful and useful, we decided not to use them, as we wouldn't utilize their features. What features are -talking about? Lazy loading, caching, dirty checking. Why don't we need them? We want to have more control -over SQL queries and minimize the object-relational impedance mismatch ourselves. Moreover, thanks to relatively -small aggregates, containing as little data as it is required to protect the invariants, we don't need the -lazy loading mechanism either. -With Hexagonal Architecture we have the ability to separate domain and persistence models and test them -independently. Moreover, we can also introduce different persistence strategies for different aggregates. -In this project, we utilize both plain SQL queries and `JdbcTemplate` and use new and very promising -project called Spring Data JDBC, that is free from the JPA-related overhead mentioned before. -Please find below an example of a repository: +### (无)ORM +如果运行 `mvn dependency:tree`,你不会找到任何 JPA 实现。尽管我们认为 ORM 解决方案(如 Hibernate) +非常强大且实用,但我们决定不使用它们,因为我们无法充分利用其特性。我们指的是哪些特性?懒加载(lazy loading)、缓存(caching)、脏检查(dirty checking)。为什么不需要?我们希望对 SQL 查询拥有更多控制权, +并自行尽量减小对象-关系阻抗不匹配(object-relational impedance mismatch)。此外,得益于相对较小的聚合—— +其中仅包含保护不变量所需的最少数据——我们也不需要 +懒加载机制。 +借助六边形架构(Hexagonal Architecture),我们能够分离领域模型与持久化模型,并 +独立地对它们进行测试。此外,我们还可以为不同聚合引入不同的持久化策略。 +在本项目中,我们同时使用原生 SQL 查询与 `JdbcTemplate`,并采用了崭新且极具前景的 +项目 Spring Data JDBC,它摆脱了前述与 JPA 相关的开销。 +下面是仓储(repository)的一个示例: ```java interface PatronEntityRepository extends CrudRepository { @@ -471,7 +426,7 @@ interface PatronEntityRepository extends CrudRepository `Import` and select attached `jvm-micrometer_rev8.json`. File has been pulled from -`https://grafana.com/grafana/dashboards/4701`. +要查看指标,你必须创建一个仪表板。前往 `Create` -> `Import`,并选择附带的 `jvm-micrometer_rev8.json`。该文件摘自 `https://grafana.com/grafana/dashboards/4701`。 -Please note application will be run with `local` Spring profile to setup some initial data. +请注意,应用程序将使用 `local` Spring profile 运行,以设置一些初始数据。 -## References +## 参考资料 1. [Introducing EventStorming](https://leanpub.com/introducing_eventstorming) by Alberto Brandolini 2. [Domain Modelling Made Functional](https://pragprog.com/book/swdddf/domain-modeling-made-functional) by Scott Wlaschin