Domain-Driven Design and Spring(自译:领域驱动设计和Spring)

1. Introduction

This script is supposed to give a brief overview about the fundamental building blocks and concepts of Domain-Driven Design focussing on their application to Java (and mostly Spring) based web applications as well as how these building blocks translate into different groups of classes with different traits.

该脚本将简述领域驱动设计的基本构建块和概念,重点介绍它们在基于Java(主要是Spring)的web应用程序中的应用,以及这些构建块如何转换为具有不同特性的不同类组。

The second part focuses on the concepts of entities, aggregate roots and repositories and how they map to Spring based Java applications.

第二部分重点介绍实体、聚合根、存储库的概念,以及它们如何映射到基于Spring的Java应用程序。

2. Domain-Driven Design

Domain-Driven Design is a book by Eric Evans and is undoubtedly one of the most important books on software design. A more compact version of the book is available as Domain-Driven Design Quickly on InfoQ.

领域驱动设计是一本由Eric Evans所著的书,无疑是最重要的软件设计书籍之一。 本书一个更精简紧凑的版本可以在InfoQ上的Domain-Driven Design Quickly找到。

The book’s core focus is on the domain a piece of software is supposed to deal with and how that domain actually connects to the software: starting with an emphasis on the domain’s ubiquitous language, approaches to distilling a model from that language and eventually turning this into working software reflecting that model.

本书的核心关注点是一个软件应该处理的领域以及该领域如何与软件实际连接:从强调领域的通用语言(UBIQUITOUS LANGUAGE的词汇包括类和主要操作的名称。)开始,从通用语言提取模型的方法,并最终将其转换成反映模型的可用软件。

While software design patterns[] are usually defined through a technical context and are used to describe and capture relationships of classes, DDD is a more wholistic approach to find a common language between programmers and business and ultimately connect the code to be written to the domain. In other words, while you might be familiar with more technical patterns already, in business code they’re mostly perceived as technical noise[]. Also, the original authors of the GoF book have shared incisive insights[] about what they’d do differently if the book ever got an update, including which patterns to deemphasize, how to slightly change the organization of the patterns etc.

软件设计模式常常通过技术上下文定义,并用于描述和捕获类之间的关系。而DDD是一种更全面的方法,可以在程序员和业务人员之间找到一种通用的语言并最终将需要编写的代码和领域连接起来。换句话说,你可能已经熟悉了更多的技术模式,但在业务代码中它们更多地被视为技术噪音。此外,GoF的原著作者也分享了他们的深刻见解——如果这本书有了更新,他们会做些什么改动,包括哪些模式需要一笔带过、如何稍微改变模式的组织等。

The building blocks define common terms that are used to describe model elements and assign certain traits to them. Let’s have a look at the most fundamental ones here.

构建块定义了用于描述模型元素并为其指定某些特征的通用术语。让我们看看最基本的。

0327-ddd

When modeling a domain any attempt to model it as a whole is doomed to fail as the sheer number of stakeholders and their different views on the domain might be very different and trying to craft a single, unique model to satisfy all of the needs might be either completely impossible or the source of great complexity in the best case. Let’s have a look at a sample sketch that outlines identified concepts in a a Point of Sales (POS) domain.

当对一个领域进行建模时,任何试图以一个整体对其建模的尝试都注定会失败,因为利益相关者的数量和他们对该领域的不同看法可能是大相径庭的。试图构建一个单一独特的模型来满足所有需求,可能要么是空中楼阁,要么在最好情况下也会极复杂。让我们看一个示例草图,它概述了销售点(POS)领域的概念。

0327-ddd-modelFigure 1. Model elements in a point of sales application

图1:销售点应用程序中的模型元素

As you can see we already grouped the model elements slightly to differentiate from more remote ones. Looking at the core concepts of customer and order here we can still identify different contexts in which the model elements just discovered might play a role in.

如你所见,我们已经对模型元素稍微分了下组,以区别较远的元素。 看看图中客户和订单的核心概念,我们依然可以确定刚刚发现的模型元素在其中发挥作用的不同上下文。

032-bounded-context

Figure 2. Identified bounded contexts

图2:确定限界上下文

Here we identified core high-level parts of the system that might all deal with the concept of a customer or an order but are usually interested in different aspects of them. The accounting context is usually interested in billing information of a customer and different payment options while the shipping context’s sole interest is usually shipping addresses and tracking an order. The order context might know about a product through its line items but actually only refers to what is essentially maintained by the catalog.

在这里我们确定了系统的核心高级部分,这些部分可能都涉及客户或订单的概念,但通常对它们的不同部分感兴趣。accounting(记账) 上下文关注客户的账单信息和不同付款方式;
shipping(送货) 上下文关注是送货地址和跟踪订单; order(订单)上下文可能通过订单项了解一个产品,但实际上仅指目录基本维护的是什么。

Domain-Driven Design and Spring(自译:领域驱动设计和Spring)

Figure 3. Model elements in bounded contexts

图3:限界上下文中的模型

This boils down to model elements which might appear as a singular concept at first glance needing to be reflected in different way in different aspects of the system. Modern software architecture even sometimes takes this a step further and even aligns system boundaries with such a bounded context, actively accepts redundancy and eventual consistency to reduce the complexity of the individual systems, fosters resilience and increases development productivity.[].

这归结为模型元素,乍看起来可能是一个单一的概念,需要在系统的不同方面以不同的方式反映出来。现代软件架构有时甚至会更进一步,甚至将系统边界与如此有限的上下文保持一致,积极接受冗余和最终一致性,以减少单个系统的复杂性,增强弹性并提高开发效率。

Represent a concept from the domain that — as the name suggests — is considered a value. In terms of classes that means that individual instances don’t have identity, no life cycle. A value object needs to be immutable to ensure integrity of the instances as they can be shared amongst different consumers. In the Java language, an instance of String is a good example of these traits, although it’s not a good example for a domain concept as it’s rather generic. A credit card number, an email address are great candidates to be modeled as value objects. Value objects are usually part of other model elements like entities or services. Implementing model elements as value objects also has a great impact on legibility and comprehensibility of the code base as Dan Bergh Johnsson demonstrates in his talk Power Use of Value Objects in DDD.

代表领域中的一个概念——顾名思义——被认为是一个值。就类而言,这意味着单个实例没有标识,没有生命周期。值对象必须是不可变的,以确保实例的完整性,因为它们可以在不同的使用者之间共享。在Java语言中,String的实例是这些特征的一个好例子,尽管它不是领域概念的好例子,因为它更加通用。
信用卡号、电子邮件地址是可以建模为值对象的理想选择。值对象通常是其他模型元素(如实体或服务)的一部分。正如Dan Bergh Johnsson在他的演讲” Power Use of Value Objects in DDD.”展示的那样,将模型元素实现为值对象也对代码的易读性和可理解性有很大影响。

Value objects are often undervalued when it comes to the translation of model into code as the implementation of a value object in plain Java is quite cumbersome due to the need to implement accessors, equals(…) and hashCode(). This can be countered with a tiny annotation processor called Lombok[].

在将模型转换为代码时,值对象经常被低估,因为在纯Java中实现值对象非常笨重繁杂,需要实现访问器、 equals(…)hashCode()。这可以通过Lombok的微型注释处理器来解决。

In comparison to value objects, an entity’s core trait is it’s identity. Two customers named Michael Müller might not constitue the very same instance, so that usually a dedicated property is introduced to capture the identity. Another core trait of entities is that they’re usually subject to a certain lifecycle within the problem domain. They get created, they undergo certain state changes usually driven by domain events and might reach an end state (i.e. might be deleted, although this doesn’t necessary mean that the information is removed from the system). Entities usually relate to other entities and contain properties that are value objects or primitives (the former preferred).

与值对象相比,实体的核心特征是身份标识。两个名为Michael Müller的客户一般不会构成同一实例,经常用一个专用属性来获取身份。实体的另一个核心特征是它们在问题域内处于特定的生命周期。它们被创建后会经历由领域事件驱动的某些状态变更,达到最终状态(即可能会被删除,尽管不一定意味着已从系统中删除)。实体和其他实体关联,包含值对象和基本元素作为属性(前者优先)。

It’s not always clear whether to model a domain concept as value object or entity. In fact the concept of an address can — depending on the context — even be modeled as both within the same application. While the address of a store might be a value object might just be part of the domain concept store, it might as well be an entity in the context of modeling a customer as shipping and billing addresses might have to be created, edited, deleted etc.

将领域建模为值对象还是实体并不总是显而易见的,实际上,地址(取决上下文语境)甚至可以在同一应用程序被建模为两类。
一个商店的地址可能是值对象,仅仅是领域概念商店的一部分;在对客户进行建模的上下文中,地址也可能是实体,因为必须能创建、编辑、删除送货地址和账单地址等。

Within the set of entities of a system, some usually play a special role in their relationship to others. Consider an order consisting of line items. The order might expose a total which is calculated from the prices of the individual line items. It might only be in a valid state if it is more than a certain minimum total. The root entities of such a construct are usually conceptually elevated to a so called aggregate root and thus create certain implications:

在系统的实体集合中,某些实体在和其他实体的关系中通常起着特殊作用。比如考虑由订单项组成的一个订单,该订单可能会暴露根据各个订单项的价格算出的总额total,只有它超过某个最小total时,才可能处于有效状态。此类构造的根实体通常在概念上提升为所谓的聚集根,从而产生某些含义:

  • The aggregate root is responsible to assert invariants on the entire aggregate. State changes on the aggregate always result in a valid result state or trigger an exception.

  • To fulfil this responsibility, only aggregate roots can be accessed by repositories (see Repositories). State changes involving non-root entities need to be applied by obtaining the aggregate root and triggering it on the root.

  • As an aggregate forms a natural consistency boundaries, references to other aggregates should be implemented in a by-id way.

  • 聚合根负责在整个聚合上维护不变量,聚合上的状态变更总是导致有效的结果状态或触发异常。
  • 要履行该职责,只有聚合根能被存储库访问(请参见存储库)。涉及非根实体的状态变更,需要通过获取聚合根并在根上触发它来应用。
  • 由于聚合形成了自然的一致性边界,因此应以by-id方式引用其他聚合。

Conceptually a repository simulates a collection of aggregate roots and allows accessing subsets or individual items. They’re usually backed by some kind of persistence mechanism but shouldn’t expose it to client code. Repositories refer to entities, not the other way round.

从概念上说,存储库模拟聚合根的集合,并允许访问子集或单个项。它们通常由某种持久化机制支持,但不应该将其公开给客户端代码。存储库涉及到实体,而不是相反。

Domain services implement functionality that cannot uniquely be assigned to an entity or value object or need to orchestrate logic between them and repositories. Business logic should be implemented in entities and value objects as much as possible as it can be tested more easily within them.

领域服务实现的功能无法唯一地分配给实体或值对象,或者说无法协调它们与存储库之间的逻辑。业务逻辑应该尽可能在实体和值对象中实现,在这里面测试会更轻松。

The mapping of a domain concept to a DDD concept has quite a few important implications for the way these concepts are reflected in the code. To work effectively with Spring based Java applications, it’s important to distinguish between that category of newables and injectables.

领域概念到DDD概念的映射对于这些概念在代码中的反映方式有重要影响。为了有效使用基于Spring的Java应用程序,区分这类newables(可更新)和injectables(可注入)非常重要。

As the names suggest, the differentiating line is drawn between the ways a developer gets hold of an instance of a class for that particular model element. A newable can be simply instantiated using the new operator, although even that should be limited to as few places as possible. The factory pattern can help here, too. Entities and value objects are newables. An injectable is usually a Spring component, which means that the latter controls its lifecycle, creates instances and destroys them. This allows the container to equip the service instance with technical services like transactions or security. Clients obtain instances by using dependency injection (hence the name injectable). Repositories and services are injectables.

顾名思义,区分线是在开发者获取特定模型元素的类实例的方式之间绘制的。可以使用new运算符简单地实例化一个newable,尽管如此也应该限制在尽可能少的地方,工厂模式在这里也可以提供帮助。newables通常是实体和值对象,而injectable通常是Spring component,意味着后者控制其生命周期、创建实例、销毁实例。这允许容器container为服务实例配置技术服务,如事务或安全性。客户端通过依赖注入(因此命名为injectable)获取实例。存储库和服务是可注入的(injectables)。

This distinction between these two groups of classes naturally defines a preferred dependency direction from injectables to newables. Generally speaking

  • Value object – JPA @Embeddable + corresponding equals(…) and hashCode() (Lombok’s @Value helps here). Can depend on other value objects and entities.

  • Entity – JPA @Entity + corresponding equals(…) and hashCode() implementations. Can depend on other entities and value objects.

  • Repository – Spring component, usually a Spring Data repository interface. Can depend on entities and value objects, are centered around entities that are aggregate roots.

  • Domain services – Usually a Spring component, a class annotated with @Component or a stereotype annotation. Can also be modeled as newables in some cases in case they don’t require technical services to be applied (e.g. security, transactions).

这两组类之间的区别自然定义了从injectables到newables优先依赖方向,一般来说:

  • 值对象——JPA @Embeddable+对应equals(…) 和hashCode() (Lombok’s@Value` 在这里可提供帮助 ),可以依赖其他实体和值对象。
  • 实体——JPA @Entity+对应 equals(…)hashCode(),可以依赖其他实体和值对象。
  • 存储库——Spring component,通常是Spring Data repository接口,可以依赖其他实体和值对象,以聚合根的实体为中心。
  • 领域服务——通常是Spring component,带有 @Component的或构造型注释的类,某些情况下,如果不需要应用技术服务(安全性、交易),也可以将其建模为newables。

Not all classes of a Spring application can be assigned to these DDD categories. These other classes can usually be grouped into the following, more technical ones:

  • Application configuration – Classes to configure components.

  • Technical adapters – Business logic implemented in a Spring application is usually exposed to clients through some remoting technology. In a web application these technologies are HTTP, HTML and JavaScript. With Spring MVC, controller classes serve the purpose of translating the concepts of the remoting technology (e.g. the notion of a request, request parameters, a payload, a response etc.) into the domain concepts and invoke services with value objects and entities.

并非Spring应用程序的所有类都能指定为这些DDD类,这些其他类通常分为以下技术性更强的类:

  • Application configuration——用于配置组件的类。
  • Technical adapters——在Spring应用程序中实现的业务逻辑通常通过某种远程技术向客户公开。 在Web应用程序中,这些技术是HTTP,HTML和JavaScript。使用Spring MVC,controller类用于将远程技术的概念(例如请求,请求参数,有效负载,响应等)转换为领域概念,并使用值对象和实体的调用服务。

3. Further reading

  1. 查看Guestbook [6]和Videoshop [7],并确保您了解哪些类实现了哪些DDD概念。
  2. 确保您了解如何实现这些概念的不同特征(实体的身份,值对象的不变性,服务的依赖注入)。

. Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson, Interview

Original: https://www.cnblogs.com/Roni-i/p/12752151.html
Author: Roni_i
Title: Domain-Driven Design and Spring(自译:领域驱动设计和Spring)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/539958/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球