NDepend Blog

Improve your .NET code quality with NDepend

Implementing a Domain with POCO (Plain Old CLR Objects)

April 8, 2021 5 minutes read

Domain-POCO-Plain-Old-CLR-Object

Here is a remark I noticed on my recent post Clean Architecture for ASP.NET Core Solution: A Case Study and I’d like to detail a bit this here:

“The article mentions that “One key fact to underline is that types inside the Domain project are Plain Old CLR Objects (POCO)” and that “business logic is in application layer”. That doesn’t fit with Domain Driven Design, but it also seems a contradiction with another paragraph where it mentions that domain contains entities and value objects, which is correct. Maybe I misread something? Business rules should be implemented in domain. In fact it should be encapsulated in domain objects. Far from domain objects being simple POCO (i.e: anemic models), they should actually have state + behavior or would otherwise violate encapsulation. The ubiquitous language is represented in the domain, and business rules are certainly domain concepts. The application is a layer that coordinates use cases by retrieving domain objects (from a Repository) and delegating into them the business logic. It can have some validation such as format, but the business rules that use state + behavior to validate and accept (or reject) commands belongs in the Domain.”

Understanding what is a POCO class

There is a misconception about what Plain Old CLR Object (POCO) are. The POCO definition on wikipedia is quite concise: “In essence, a POCO does not have any dependency on an external framework.”. To clarify even further a domain made of POCOs is implemented by a group of classes ; interfaces ; structures ; enumerations ; exceptions that only depend on basic CLR types including: string, char, bool, integer representations (Int32, UInt64…), float representations (double, float, decimal), date representations and exception.

The POCO classes of the domain represent the entities and the value objects. They must not depend on any persistence infrastructure framework like Entity Framework or NHibernate. This is the Persistence Ignorance principle. Entity Framework Core and other modern ORM frameworks are designed with this principle in mind. For example they don’t require the domain classes to derive from a particular class to persist their states.

The persistence is just one aspect of the infrastructure that surrounds an application. This is why the persistence ignorance principle extends to the Infrastructure Ignorance principle. The domain entities must not depend on anything related to UI, network, hosting or other infrastructure aspects. The two goals of the domain infrastructure ignorance are:

  • This lets the team focuses on core domain aspects by ignoring infrastructure stuff that would make the domain more complex and less maintainable.
  • This will avoid drama when there is a need to change an infrastructure technology, which is likely to happen in the future.

Let’s notice that some .NET Base Class Library (BCL) types like System.IComparable, LINQ or System.Math can be used by the domain. This doesn’t contradict with the two goals above. Those types proposes some standard patterns to simplify .NET code. Moreover the domain is bound with the .NET platform. Migrating it to another platform will provoke drama any way.

Domain’s POCO classes can and should contain logic

In the original remark there was the misconception that using POCO should necessarily leads to an Anemic Domain Model: a domain that contains entities and value objects implemented through properties-only classes without any logic. Here is a quote from Martin Fowler about that:

“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.”

To prevent the anemic domain model anti-pattern, the domain POCO classes must implement all the domain logic that relies only on those aforementioned basics types and POCO objects themselves. This is the logic that don’t require any infrastructure call.

Example of Business Rules that fit and not fit into the Domain

Part of the logic usually implemented in the domain is validation: refusing state mutations that lead to corrupted states. For example, the domain of a job board enterprise can implement this rule A) “avoid accepting new applicant for a job when the job has already been assigned to someone”. Having applicants for a job-closed is considered as a corrupted state. A dedicated domain exception CantAcceptApplicantForAJobClosedException can be created to inform callers of an attempt to violate this rule.

On the other hand, this rule B) “when a job gets assigned to an applicant, notify with an email all other applicants that the job is closed” doesn’t fit in the domain: POCO shouldn’t know about a framework that sends email, nor about an abstraction that wraps email sending. Thus, such send-email abstraction:

  • must be declared in the Application layer,
  • must be implemented in the Infrastructure layer
  • can be consumed from the Application layer (for example consumed by the rule B implemented in the Application layer)
  • cannot be consumed from the domain, since the Domain layer doesn’t depend on the Application layer

As a reminder from my previous post on the Jason Taylor’s Clean Architecture application, the Infrastructure implementations are injected in the Application layer by a higher-level layer that relies on both Infrastructure and Application. In this application the WebUI layer does the injection at application starting time.

Clean Architecture Diagram

Application Business Rules vs. Enterprise Business Rules

Here is a common Clean Architecture terminology used to segregate business rules:

  • Enterprise Business Rules: Those rules are not specific to an application and should be implemented within the domain itself. The rule A) “avoid accepting new applicant for a job when the job has already been assigned to someone” can be implemented only with POCO classes and fits well in the domain.
  • Application Business Rules: Those rules are specific to an application. They specify how the application harness the enterprise business rules in order to execute an action, like notifying a user. The rule B) “when a job gets assigned to an applicant, notify with an email all other applicants that the job is closed” is an action. It is specific to the application that takes care of the relation with applicants. It doesn’t fit in the domain.

Comments:

  1. Great article Patrick. From my own experience it’s hard to get these ideas across but this and your previous article do a good job and I’ll be referencing them to help others understand the motivation behind our solution architecture.

Comments are closed.