【Domain】领域模型(Domain Model)

Posted by 西维蜀黍 on 2023-08-23, Last Modified on 2023-09-05

Domain Models

A domain model is a system of abstractions that describes selected aspects of a sphere of knowledge, influence or activity (a domain). The model can then be used to solve problems related to that domain. The domain model is a representation of meaningful real-world concepts pertinent to the domain that need to be modeled in software. The concepts include the data involved in the business and rules the business uses in relation to that data. A domain model leverages natural language of the domain.

A domain model generally uses the vocabulary of the domain, thus allowing a representation of the model to be communicated to non-technical stakeholders. It should not refer to any technical implementations such as databases or software components that are being designed.

Rich Domain model versus Anemic Domain Models(贫血领域对象)

Anemic Domain Models

贫血领域对象(Anemic Domain Object)是指仅用作数据载体,而没有行为和动作的领域对象。

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters.

Of course, when you use an anemic domain model, those data models will be used from a set of service objects (traditionally named the business layer) which capture all the domain or business logic. The business layer sits on top of the data model and uses the data model just as data.

The anemic domain model is just a procedural style design. Anemic entity objects are not real objects because they lack behavior (methods). They only hold data properties and thus it is not object-oriented design. By putting all the behavior out into service objects (the business layer), you essentially end up with spaghetti code or transaction scripts, and therefore you lose the advantages that a domain model provides.

Regardless, if your microservice or Bounded Context is very simple (a CRUD service), the anemic domain model in the form of entity objects with just data properties might be good enough, and it might not be worth implementing more complex DDD patterns. In that case, it will be simply a persistence model, because you have intentionally created an entity with only data for CRUD purposes.

That is why microservices architectures are perfect for a multi-architectural approach depending on each Bounded Context. For instance, in eShopOnContainers, the ordering microservice implements DDD patterns, but the catalog microservice, which is a simple CRUD service, does not.

Some people say that the anemic domain model is an anti-pattern. It really depends on what you are implementing. If the microservice you are creating is simple enough (for example, a CRUD service), following the anemic domain model it is not an anti-pattern. However, if you need to tackle the complexity of a microservice’s domain that has a lot of ever-changing business rules, the anemic domain model might be an anti-pattern for that microservice or Bounded Context. In that case, designing it as a rich model with entities containing data plus behavior as well as implementing additional DDD patterns (aggregates, value objects, etc.) might have huge benefits for the long-term success of such a microservice.

Data Model/View Models/Data Transfer Objects (DTOs)

失血模型

失血模型就是纯数据POJO,业务逻辑完全与entity分离。

Domain Object 只有属性的 getter/setter 方法的纯数据类,所有的业务逻辑完全由 business object 来完成。

public class Article implements Serializable {
    private Integer id;
    private String title;
    private Integer classId;
    private Integer authorId;
    private String authorName;
    private String content;
    private Date pubDate;
    //getter/setter/toString
}

public interface ArticleDao {
     public Article getArticleById(Integer id);
     public Article findAll();
     public void updateArticle(Article article);
}

即我们把对于 Article 的业务操作都暴露且放在了service层,Article 实体本身只被用于存储数据。

面向对象的定义是什么,对象有属性、有行为。

因而,通过实现失血模型的方式实现,这其实不是面向对象(object-oriented design),而是面向过程(procedural style design),因为这样实现破坏了对象的封装性。

Anemic Domain Models(贫血领域对象)

贫血模型domain object中含有与持久化无关的逻辑,不依赖于 DAO 层。

简单来说,就是 Domain Object 包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到 Service 层。

public class Article implements Serializable {
    private Integer id;
    private String title;
    private Integer classId;
    private Integer authorId;
    private String authorName;
    private String content;
    private Date pubDate;
    //getter/setter/toString
    //判断是否是热门分类(假设等于57或102的类别的文章就是热门分类的文章)
    public boolean isHotClass(Article article){
        return Stream.of(57,102)
            .anyMatch(classId -> classId.equals(article.getClassId()));
    }
    //更新分类,但未持久化,这里不能依赖Dao去操作实体化
    public Article changeClass(Article article, ArticleClass ac){
        return article.setClassId(ac.getId());
    }
}

@Repository("articleDao")
public class ArticleDaoImpl implements ArticleDao{
    @Resource
    private ArticleDao articleDao;
    public void changeClass(Article article, ArticleClass ac){
        article.changeClass(article, ac);
        articleDao.update(article)
    }
}

注意这个模式不在 Domain 层里依赖 DAO。持久化的工作还需要在 DAO 或者 Service 中进行。

值得注意的是,通过实现贫血模型的方式实现,这仍然不是面向对象,而是面向过程。

这样做的优缺点

  • 优点:各层单向依赖,结构清晰。

  • 缺点

    • Domain Object 的部分比较紧密依赖的持久化 Domain Logic 被分离到 Service 层,显得不够 OO

    • Service 层过于厚重

充血模型(Full Domain Models)

充血模型把domain object和business object合二为一,service层不依赖 DAO层,而entity层与 DAO 层形成双向依赖。

充血模型和第二种模型差不多,区别在于实体的行为逻辑位于哪一层。,充血模型会将实体的行为逻辑放到 Domain 中,以更加高内聚,Service 是很薄的一层,封装实体与实体之间交互的逻辑,并且不和 DAO 打交道:

Service (事务封装) —> Domain Object <—> DAO

public class Article implements Serializable {
    @Resource
    private static ArticleDao articleDao;
    private Integer id;
    private String title;
    private Integer classId;
    private Integer authorId;
    private String authorName;
    private String content;
    private Date pubDate;
    //getter/setter/toString
    //使用articleDao进行持久化交互
    public List<Article> findAll(){
        return articleDao.findAll();
    }
    //判断是否是热门分类(假设等于57或102的类别的文章就是热门分类的文章)
    public boolean isHotClass(Article article){
        return Stream.of(57,102)
            .anyMatch(classId -> classId.equals(article.getClassId()));
    }
    //更新分类,但未持久化,这里不能依赖Dao去操作实体化
    public Article changeClass(Article article, ArticleClass ac){
        return article.setClassId(ac.getId());
    }
}

所有实体的数据和行为都在 Domain 中,事务管理也在 Item 中实现。这样做的优缺点如下。

优点:

  • 更加符合 OO 的原则;
  • Service 层很薄,只充当 Facade 的角色,不和 DAO 打交道。

缺点:

  • DAO 和 Domain Object 形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。
  • 如何划分 Service 层逻辑和 Domain 层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能 导致整个结构的混乱无序。

Discussion from Martin Fowler

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic, carrying out all the computation and updating the model objects with the results. These services live on top of the domain model and use the domain model for data.

The fundamental horror of this anti-pattern is that it’s so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What’s worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.

Now object-oriented purism is all very well, but I realize that I need more fundamental arguments against this anemia. In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits. The primary cost is the awkwardness of mapping to a database, which typically results in a whole layer of O/R mapping. This is worthwhile iff you use the powerful OO techniques to organize complex logic. By pulling all the behavior out into services, however, you essentially end up with Transaction Scripts, and thus lose the advantages that the domain model can bring. As I discussed in P of EAA, Domain Models aren’t always the best tool.

It’s also worth emphasizing that putting behavior into the domain objects should not contradict the solid approach of using layering to separate domain logic from such things as persistence and presentation responsibilities. The logic that should be in a domain object is domain logic - validations, calculations, business rules - whatever you like to call it. (There are cases when you make an argument for putting data source or presentation logic in a domain object, but that’s orthogonal to my view of anemia.)

One source of confusion in all this is that many OO experts do recommend putting a layer of procedural services on top of a domain model, to form a Service Layer. But this isn’t an argument to make the domain model void of behavior, indeed service layer advocates use a service layer in conjunction with a behaviorally rich domain model.

Eric Evans’s excellent book Domain Driven Design has the following to say about these layers.

Application Layer [his name for Service Layer]: Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. The tasks this layer is responsible for are meaningful to the business or necessary for interaction with the application layers of other systems. This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down. It does not have state reflecting the business situation, but it can have state that reflects the progress of a task for the user or the program.

Domain Layer (or Model Layer): Responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.

The key point here is that the Service Layer is thin - all the key logic lies in the domain layer. He reiterates this point in his service pattern:

Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming.

I don’t know why this anti-pattern is so common. I suspect it’s due to many people who haven’t really worked with a proper domain model, particularly if they come from a data background. Some technologies encourage it; such as J2EE’s Entity Beans which is one of the reasons I prefer POJO domain models.

In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you’ve robbed yourself blind.

From https://martinfowler.com/bliki/AnemicDomainModel.html

胀血模型

胀血模型直接取消service层,只剩下entity层与 DAO 层。

基于充血模型的第三个缺点,有同学提出,干脆取消 Service 层,只剩下 Domain Object 和 DAO 两层,在 Domain Object 的 Domain Logic 上面封装事务。

Domain Object (事务封装,业务逻辑) <—> DAO

似乎 Ruby on rails 就是这种模型,它甚至把 Domain Object 和 DAO 都合并了。

这样做的优缺点:

  • 简化了分层
  • 也算符合 OO

该模型缺点:

  • 很多不是 Domain Logic 的 Service 逻辑也被强行放入 Domain Object ,引起了 Domain Object 模型的不稳定;
  • Domain Object 暴露给 Web 层过多的信息,可能引起意想不到的副作用。

Reference

Martin Fowler

Misc