Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concrete examples on why the 'Anemic Domain Model' is considered an anti-pattern [closed]

I apologize if this is a duplicate, but I couldn't find any concrete examples on the topic in related questions.

After reading Martin Fowler's article on the 'Anemic Domain Model', I'm left wandering as to why is this considered an anti-pattern. Even does the majority of enterprise developers consider it an anti-pattern, since AFAIK probably 90% of the j2ee applications are designed in an 'anemic' way ?

Can someone recommend further reading on the topic (other than the 'Domain Driven Design' book), or even better, give a concrete examples on how this anti-pattern is affecting application design in a bad way.

Thanks,

like image 315
Simeon Avatar asked Jun 09 '11 14:06

Simeon


People also ask

Why are anemic domain model considered an anti-pattern?

An anemic model. In an anemic model you invoke a method and pass it the anemic model to bring the anemic model to a legal state. Therefore the anemic model's state management is placed outside the anemic model and this fact makes it an anti-pattern from an object oriented perspective.

What is an anemic domain model?

The anemic domain model is a programming anti-pattern where the domain objects contain little or no business logic like validations, calculations, rules, and so forth. The business logic is thus baked into the architecture of the program itself, making refactoring and maintenance more difficult and time-consuming.

What is an anemic object?

In object-oriented programming, and especially in Domain-Driven Design, objects are said to be anemic if they have state but lack behavior.

What is rich domain model?

A Rich Domain Model is the technical part when applying DDD, it envolves the building blocks like Entity, Value Objects and Aggregate Root. The goal is to build a ubiquitous language between developers and stakeholders using the a vocabulary that describes the business rules.


2 Answers

Martin Fowler brings this industry many words and less understanding.

Majority of applications today (web/db) do need many objects that expose their properties.

Any authority (self claimed) frowning upon such practice should lead by example, and show us a successful real world application that's full of embodiments of his marvelous principles.

Or else shut up. It is sickening that there so many hot airs in our industry. This is engineering, not a drama club.

like image 60
irreputable Avatar answered Oct 06 '22 09:10

irreputable


For the complete answer take a look at my blog that also contains source code examples [blog]: https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

If you look at the anemic domain model from an object oriented perspective it is definitely an anti-pattern because it is pure procedural programming. The reason why it is called an anti-pattern is that the main object oriented principle is not covered by an anemic domain model:

Object oriented means that: an object manages its state and guarantees that it is in a legal state at any time. (data hiding, encapsulation)

Therefore an object encapsulates data and manages the access and interpretation of it. In contrast to this an anemic model does not gurantee that it is in a legal state at any time.

An example of an order with order items will help to show the difference. So let's take a look at an anemic model of an order.

An anemic model

 public class Order {
    private BigDecimal total = BigDecimal.ZERO;
    private List<OrderItem> items = new ArrayList<OrderItem>();

    public BigDecimal getTotal() {
        return total;
    }

    public void setTotal(BigDecimal total) {
        this.total = total;
    }

    public List<OrderItem> getItems() {
        return items;
    }

    public void setItems(List<OrderItem> items) {
        this.items = items;
    }
}

public class OrderItem {

    private BigDecimal price = BigDecimal.ZERO;
    private int quantity;
    private String name;
    
    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}

So where is the logic located that interprets the order and order items to calculate an order total? This logic is often placed in classes named *Helper, *Util, *Manager or simply *Service. An order service in an anemic model would look like this:

public class OrderService {
    public void calculateTotal(Order order) {
        if (order == null) {
             throw new IllegalArgumentException("order must not be null");
        }

        BigDecimal total = BigDecimal.ZERO;
        List<OrderItem> items = order.getItems();

        for (OrderItem orderItem : items) {
            int quantity = orderItem.getQuantity();
            BigDecimal price = orderItem.getPrice();
            BigDecimal itemTotal = price.multiply(new BigDecimal(quantity));
            total = total.add(itemTotal);
        }
        order.setTotal(total);
    }
}

In an anemic model you invoke a method and pass it the anemic model to bring the anemic model to a legal state. Therefore the anemic model's state management is placed outside the anemic model and this fact makes it an anti-pattern from an object oriented perspective.

Sometimes you will see a slightly different service implementation that does not modify the anemic model. Instead it returns the value it calculates. E.g.

public BigDecimal calculateTotal(Order order); 

In this case the Order doesn't have a property total. If you now make the Order immutable you are on the way to functional programming. But this is another topic that I can't discover here.

The problems with the anemic order model above are:

  • If someone adds an OrderItem to the Order the Order.getTotal() value is incorrect as long as it has not been recalculated by the OrderService. In a real world application it can be cumbersome to find out who added the order item and why the OrderService has not been called. As you might have recognized already the Order also breaks encapsulation of the order items list. Someone can call order.getItems().add(orderItem) to add an order item. That can make it difficult to find the code that really adds the item (order.getItems() reference can be passed through the whole application).
  • The OrderService's calculateTotal method is responsible for calculating the total for all Order objects. Therefore it must be stateless. But stateless also means that it can not cache the total value and only recalculate it if the Order object changed. So if the calculateTotal method takes a long time you also have a performance issue. Nevertheless you will have performance issues, because clients might not know if the Order is in a legal state or not and therefore preventatively call calculateTotal(..) even when it is not needed.

You will also see sometimes that services do not update the anemic model and instead just return the result. E.g.

public class OrderService {
    public BigDecimal calculateTotal(Order order) {
        if (order == null) {
             throw new IllegalArgumentException("order must not be null");
        }

        BigDecimal total = BigDecimal.ZERO;
        List<OrderItem> items = order.getItems();

        for (OrderItem orderItem : items) {
            int quantity = orderItem.getQuantity();
            BigDecimal price = orderItem.getPrice();
            BigDecimal itemTotal = price.multiply(new BigDecimal(quantity));
            total = total.add(itemTotal);
        }
       return total;
    }
}

In this cases the services interpret the state of the anemic model at some time and do not update the anemic model with the result. The only benefit of this approach is that the anemic model can not contain an invalid total state, because it won't have a total property. But this also means that the total must be calculated every time it is needed. By removing the total property you lead developers to use the service and to not to rely on the total's property state. But this will not guarantee that the developers cache the total value in some way and thus they might also use values that are outdated. This way of implementing a service can be done whenever a property is derived form another property. Or in other words... when you interpret basic data. E.g. int getAge(Date birthday).

Now take a look at the rich domain model to see the difference.

The rich domain approach

public class Order {

    private BigDecimal total;
    private List<OrderItem> items = new ArrayList<OrderItem>();

    /**
      * The total is defined as the sum of all {@link OrderItem#getTotal()}.
      *
      * @return the total of this {@link Order}.
      */
    public BigDecimal getTotal() {
        if (total == null) {
           /*
            * we have to calculate the total and remember the result
            */
           BigDecimal orderItemTotal = BigDecimal.ZERO;
           List<OrderItem> items = getItems();

           for (OrderItem orderItem : items) {
               BigDecimal itemTotal = orderItem.getTotal();
               /*
                * add the total of an OrderItem to our total.
                */
               orderItemTotal = orderItemTotal.add(itemTotal);
           }

           this.total = orderItemTotal;
           }
        return total;
        }

   /**
    * Adds the {@link OrderItem} to this {@link Order}.
    *
    * @param orderItem
    *            the {@link OrderItem} to add. Must not be null.
    */
    public void addItem(OrderItem orderItem) {
        if (orderItem == null) {
            throw new IllegalArgumentException("orderItem must not be null");
        }
        if (this.items.add(orderItem)) {
           /*
            * the list of order items changed so we reset the total field to
            * let getTotal re-calculate the total.
            */ 
            this.total = null;
        }
    }

    /**
      *
      * @return the {@link OrderItem} that belong to this {@link Order}. Clients
      *         may not modify the returned {@link List}. Use
      *         {@link #addItem(OrderItem)} instead.
      */
    public List<OrderItem> getItems() {
       /*
        * we wrap our items to prevent clients from manipulating our internal
        * state.
        */
        return Collections.unmodifiableList(items);
    }

}

public class OrderItem {

    private BigDecimal price;

    private int quantity;

    private String name = "no name";

    public OrderItem(BigDecimal price, int quantity, String name) {
     if (price == null) {
      throw new IllegalArgumentException("price must not be null");
     }
     if (name == null) {
      throw new IllegalArgumentException("name must not be null");
     }
     if (price.compareTo(BigDecimal.ZERO) < 0) {
      throw new IllegalArgumentException(
        "price must be a positive big decimal");
     }
     if (quantity < 1) {
      throw new IllegalArgumentException("quantity must be 1 or greater");
     }
     this.price = price;
     this.quantity = quantity;
     this.name = name;
    }

    public BigDecimal getPrice() {
     return price;
    }

    public int getQuantity() {
     return quantity;
    }

    public String getName() {
     return name;
    }

    /**
      * The total is defined as the {@link #getPrice()} multiplied with the
      * {@link #getQuantity()}.
      *
      * @return
      */
    public BigDecimal getTotal() {
     int quantity = getQuantity();
      BigDecimal price = getPrice();
      BigDecimal total = price.multiply(new BigDecimal(quantity));
     return total;
    }
}

The rich domain model respects the object oriented principles and gurantees that it is in a legal state at any time.

References

  • Blog: https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
  • Sources: https://github.com/link-intersystems/blog/tree/master/anemic-vs-rich-domain-model
like image 61
René Link Avatar answered Oct 06 '22 08:10

René Link