programing tip

리치 vs 빈혈 도메인 모델

itbloger 2020. 10. 12. 07:18
반응형

리치 vs 빈혈 도메인 모델


저는 빈혈 도메인 모델보다 리치 도메인 모델을 사용해야하는지 결정하고 있으며 두 가지의 좋은 예를 찾고 있습니다.

저는 서비스-> 리포지토리-> 스토리지 계층 시스템에서 지원하는 Anemic 도메인 모델을 사용하여 웹 애플리케이션을 구축하고 있으며 , BL 유효성 검사를 위해 FluentValidation사용 하고 모든 BL을 서비스 계층에 배치했습니다.

나는 Eric Evan의 DDD 책을 읽었고, 그는 (Fowler와 다른 사람들과 함께) Anemic Domain Models가 반 패턴이라고 생각하는 것 같습니다.

그래서 저는이 문제에 대한 통찰력을 얻고 싶었습니다.

또한 Rich Domain 모델의 좋은 (기본) 예제와 그것이 제공하는 Anemic Domain Model에 비해 이점을 찾고 있습니다.


Bozhidar Bozhanov는 블로그 게시물 에서 빈혈 모델에 찬성하는 것으로 보입니다 .

다음은 그가 제시하는 요약입니다.

  • 도메인 객체는 스프링 (IoC)으로 관리되어서는 안되며, DAO 또는 인프라와 관련된 어떤 것도 주입되어서는 안됩니다.

  • 도메인 개체는 최대 절전 모드 (또는 지속성 메커니즘)에 의해 설정된 도메인 개체가 있습니다.

  • 도메인 개체는 DDD의 핵심 아이디어처럼 비즈니스 논리를 수행하지만 여기에는 데이터베이스 쿼리 또는 CRUD가 포함되지 않고 개체의 내부 상태에 대한 작업 만 포함됩니다.

  • DTO가 거의 필요하지 않습니다. 도메인 개체는 대부분의 경우 DTO 자체입니다 (일부 상용구 코드 절약).

  • 서비스는 CRUD 작업을 수행하고, 이메일을 보내고, 도메인 개체를 조정하고, 여러 도메인 개체를 기반으로 보고서를 생성하고, 쿼리를 실행합니다.

  • 서비스 (애플리케이션) 계층은 그다지 얇지는 않지만 도메인 개체에 고유 한 비즈니스 규칙은 포함하지 않습니다.

  • 코드 생성은 피해야합니다. 추상화, 디자인 패턴 및 DI는 코드 생성의 필요성을 극복하고 궁극적으로 코드 중복을 제거하는 데 사용되어야합니다.

최신 정보

필자는 최근 저자가 일종의 하이브리드 접근 방식을 옹호하는 기사를 읽었습니다. 도메인 객체는 상태에 따라 다양한 질문에 답할 수 있습니다 (완전히 빈약 한 모델의 경우 서비스 계층에서 수행 될 수 있음).


차이점은 빈약 한 모델은 논리와 데이터를 분리한다는 것입니다. 논리는 종종라는 이름의 클래스에 배치됩니다 **Service, **Util, **Manager, **Helper과에 있도록. 이러한 클래스는 데이터 해석 논리를 구현하므로 데이터 모델을 인수로 사용합니다.

public BigDecimal calculateTotal(Order order){
...
}

리치 도메인 접근 방식은 데이터 해석 로직을 리치 도메인 모델에 배치하여이를 역전시킵니다. 따라서 논리와 데이터를 결합하고 풍부한 도메인 모델은 다음과 같습니다.

order.getTotal();

이것은 객체 일관성에 큰 영향을 미칩니다. 데이터 해석 로직이 데이터를 감싸기 때문에 (데이터는 객체 메서드를 통해서만 액세스 할 수 있음) 메서드는 다른 데이터의 상태 변경에 반응 할 수 있습니다.-> 이것이 우리가 행동이라고 부르는 것입니다.

빈약 한 모델에서 데이터 모델은 그들이 할 수있는 풍부한 도메인 모델에있는 동안 합법적 인 상태에 있음을 보장 할 수 없습니다. 풍부한 도메인 모델은 캡슐화, 정보 숨기기 및 데이터와 논리를 함께 가져 오는 것과 같은 OO 원칙을 적용하므로 빈혈 모델은 OO 관점에서 반 패턴입니다.

더 깊은 통찰력을 얻으려면 내 블로그 https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/를 살펴보십시오.


내 관점은 다음과 같습니다.

빈약 한 도메인 모델 = 개체에 매핑 된 데이터베이스 테이블 (필드 값만, 실제 동작 없음)

풍부한 도메인 모델 = 동작을 노출하는 개체 모음

간단한 CRUD 애플리케이션을 만들고 싶다면 클래식 MVC 프레임 워크가있는 빈약 한 모델이면 충분합니다. 그러나 어떤 종류의 논리를 구현하려는 경우 빈혈 모델은 객체 지향 프로그래밍을 수행하지 않음을 의미합니다.

* 객체 동작은 지속성과 관련이 없습니다. 다른 계층 (데이터 매퍼, 리포지토리 등)은 도메인 개체를 유지하는 역할을합니다.


먼저이 기사 http://msdn.microsoft.com/en-gb/magazine/dn385704.aspx 의 답변을 복사하여 붙여 넣었습니다 .

그림 1은 기본적으로 게터와 세터가있는 스키마 인 Anemic Domain Model을 보여줍니다.

Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables

public class Customer : Person
{
  public Customer()
  {
    Orders = new List<Order>();
  }
  public ICollection<Order> Orders { get; set; }
  public string SalesPersonId { get; set; }
  public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string CompanyName { get; set; }
  public string EmailAddress { get; set; }
  public string Phone { get; set; }
}

이 풍부한 모델에서 단순히 읽고 쓸 속성을 ​​노출하는 것이 아니라 고객의 공개 표면은 명시 적 메서드로 구성됩니다.

Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties

public class Customer : Contact
{
  public Customer(string firstName, string lastName, string email)
  {
    FullName = new FullName(firstName, lastName);
    EmailAddress = email;
    Status = CustomerStatus.Silver;
  }
  internal Customer()
  {
  }
  public void UseBillingAddressForShippingAddress()
  {
    ShippingAddress = new Address(
      BillingAddress.Street1, BillingAddress.Street2,
      BillingAddress.City, BillingAddress.Region,
      BillingAddress.Country, BillingAddress.PostalCode);
  }
  public void CreateNewShippingAddress(string street1, string street2,
   string city, string region, string country, string postalCode)
  {
    ShippingAddress = new Address(
      street1,street2,
      city,region,
      country,postalCode)
  }
  public void CreateBillingInformation(string street1,string street2,
   string city,string region,string country, string postalCode,
   string creditcardNumber, string bankName)
  {
    BillingAddress = new Address      (street1,street2, city,region,country,postalCode );
    CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
  }
  public void SetCustomerContactDetails
   (string email, string phone, string companyName)
  {
    EmailAddress = email;
    Phone = phone;
    CompanyName = companyName;
  }
  public string SalesPersonId { get; private set; }
  public CustomerStatus Status { get; private set; }
  public Address ShippingAddress { get; private set; }
  public Address BillingAddress { get; private set; }
  public CustomerCreditCard CreditCard { get; private set; }
}

풍부한 도메인 클래스의 이점 중 하나는 모든 계층의 개체에 대한 참조가있을 때마다 해당 동작 (메서드)을 호출 할 수 있다는 것입니다. 또한 함께 협력하는 작고 분산 된 방법을 작성하는 경향이 있습니다. 빈약 한 도메인 클래스에서는 일반적으로 사용 사례에 따라 구동되는 서비스 계층에서 복잡한 절차 방법을 작성하는 경향이 있습니다. 일반적으로 풍부한 도메인 클래스에 비해 유지 관리가 어렵습니다.

동작이있는 도메인 클래스의 예 :

class Order {

     String number

     List<OrderItem> items

     ItemList bonus

     Delivery delivery

     void addItem(Item item) { // add bonus if necessary }

     ItemList needToDeliver() { // items + bonus }

     void deliver() {
         delivery = new Delivery()
         delivery.items = needToDeliver()
     }

}

메서드 needToDeliver()는 보너스를 포함하여 배달해야하는 항목 목록을 반환합니다. 클래스 내부, 다른 관련 클래스 또는 다른 레이어에서 호출 할 수 있습니다. 예를 들어 Order보기로 전달 하면 needToDeliver()선택한 Order항목을 사용하여 사용자가 저장 버튼을 클릭하여 Order.

Responding To Comment

This is how I use the domain class from controller:

def save = {
   Order order = new Order()
   order.addItem(new Item())
   order.addItem(new Item())
   repository.create(order)
}

The creation of Order and its LineItem is in one transaction. If one of the LineItem can't be created, no Order will be created.

I tend to have method that represent a single transaction, such as:

def deliver = {
   Order order = repository.findOrderByNumber('ORDER-1')
   order.deliver()       
   // save order if necessary
}

Anything inside deliver() will be executed as one single transaction. If I need to execute many unrelated methods in a single transaction, I would create a service class.

To avoid lazy loading exception, I use JPA 2.1 named entity graph. For example, in controller for delivery screen, I can create method to load delivery attribute and ignore bonus, such as repository.findOrderByNumberFetchDelivery(). In bonus screen, I call another method that load bonus attribute and ignore delivery, such as repository.findOrderByNumberFetchBonus(). This requires dicipline since I still can't call deliver() inside bonus screen.


When I used to write monolithic desktop apps I built rich domain models, used to enjoy building them.

Now I write tiny HTTP microservices, there's as little code as possible, including anemic DTOs.

I think DDD and this anemic argument date from the monolithic desktop or server app era. I remember that era and I would agree that anemic models are odd. I built a big monolithic FX trading app and there was no model, really, it was horrible.

With microservices, the small services with their rich behaviour, are arguably the composable models and aggregates within a domain. So the microservice implementations themselves may not require further DDD. The microservice application may be the domain.

An orders microservice may have very few functions, expressed as RESTful resources or via SOAP or whatever. The orders microservice code may be extremely simple.

A larger more monolithic single (micro)service, especially one that keeps it model in RAM, may benefit from DDD.


Here is a example that might help:

Anemic

class Box
{
    public int Height { get; set; }
    public int Width { get; set; }
}

Non-anemic

class Box
{
    public int Height { get; private set; }
    public int Width { get; private set; }

    public Box(int height, int width)
    {
        if (height <= 0) {
            throw new ArgumentOutOfRangeException(nameof(height));
        }
        if (width <= 0) {
            throw new ArgumentOutOfRangeException(nameof(width));
        }
        Height = height;
        Width = width;
    }

    public int area()
    {
       return Height * Width;
    }
}

Anemic domain models are important for ORM and easy transfer over networks (the life-blood of all comercial applications) but OO is very important for encapsulation and simplifying the 'transactional/handling' parts of your code.

Therefore what is important is being able to identify and convert from one world to the other.

Name Anemic models something like AnemicUser, or UserDAO etc so developers know there is a better class to use, then have an appropriate constructor for the none Anemic class

User(AnemicUser au)

and adapter method to create the anemic class for transporting/persistence

User::ToAnemicUser() 

Aim to use the none Anemic User everywhere outside of transport/persistence


The classical approach to DDD doesn't state to avoid Anemic vs Rich Models at all costs. However, MDA can still apply all DDD concepts (bounded contexts, context maps, value objects, etc.) but use Anemic vs Rich models in all cases. There are many cases where using Domain Services to orchestrate complex Domain Use Cases across a set of domain aggregates as being a much better approach than just aggregates being invoked from application layer. The only difference from the classical DDD approach is where does all validations and business rules reside? There’s a new construct know as model validators. Validators ensure the integrity of the full input model prior to any use case or domain workflow takes place. The aggregate root and children entities are anemic but each can have their own model validators invoked as necessary, by it’s root validator. Validators still adhered to SRP, are easy to maintain and are unit testable.

The reason for this shift is we’re now moving more towards an API first vs an UX first approach to Microservices. REST has played a very important part in this. The traditional API approach (because of SOAP) was initially fixated on a command based API vs. HTTP verbs (POST, PUT, PATCH, GET and DELETE). A command based API fits well with the Rich Model object oriented approach and is still very much valid. However, simple CRUD based APIs, although they can fit within a Rich Model, is much better suited with simple anemic models, validators and Domain Services to orchestrate the rest.

I love DDD in all that it has to offer but there comes a time you need stretch it a bit to fit constantly changing and better approach to architecture.

참고URL : https://stackoverflow.com/questions/23314330/rich-vs-anemic-domain-model

반응형