Back to Home

Article

Architectural Layers and Modeling Domain Logic

November 14, 2016
By Lorenzo Dee
By Lorenzo Dee
Reading Time : 10
 minute
s
Architectural Layers Image
Back to Home

Article

Architectural Layers and Modeling Domain Logic

November 14, 2016
By Lorenzo Dee
By Lorenzo Dee
Reading Time : 10
 minute
s

Lorenzo corrects the impression that the domain model pattern is always the best to use. He provides options and tips on when to use (and not to use) the domain model pattern.

As I was discussing the PoEAA patterns used to model domain logic (i.e. transaction script, table module, domain model), I noticed that people get the impression (albeit wrong impression) that the domain model pattern is best. So, they set out to apply it on everything.

Not Worthy of Domain Model Pattern

Let’s get real. The majority of sub-systems are CRUD-based. Only a certain portion of the system requires the domain model implementation pattern. Or, put it in another way, there are parts of the application that just needs forms over data, and some validation logic (e.g. required/mandatory fields, min/max values on numbers, min/max length on text). For these, the domain model is not worth the effort.

For these, perhaps an anemic domain model would fit nicely.

Anemic Domain Model Isn’t As Bad As It Sounds

The anemic domain model isn’t as bad as it sounds. There, I said it (at least here in my blog post).

But how does it look like?

Class Diagram 1 Image
package com.acme.bc.domain.model;
...
@Entity
class Person {
  @Id ... private Long id;
  private String firstName;
  private String lastName;
  // ...
  // getters and setters
}
...
interface PersonRepository /* extends CrudRepository<Person, Long> */ {
  // CRUD methods (e.g. find, find/pagination, update, delete)
}
package com.acme.bc.infrastructure.persistence;
...
class PersonRepositoryJpa implements PersonRepository {
  ...
}

In the presentation layer, the controllers can have access to the repository. The repository does its job of abstracting persistence details.

package com.acme.bc.interfaces.web;
 
@Controller
class PersonsController {
  private PersonRepository personRepository;
  public PersonsController(PersonRepository personRepository) {...}
  // ...
}

In this case, having the Person class exposed to the presentation layer is perfectly all right. The presentation layer can use it directly, since it has a public zero-arguments constructor, getters and setters, which are most likely needed by the view.

And there you have it. A simple CRUD-based application.

Do you still need a service layer? No. Do you still need DTO (data transfer objects)? No. In this simple case of CRUD, you don’t need additional services or DTOs.

Yes, the Person looks like a domain entity. But it does not contain logic, and is simply used to transfer data. So, it’s really just a DTO. But this is all right since it does the job of holding the data stored-to and retrieved-from persistence.

Now, if the business logic starts to get more complicated, some entities in the initially anemic domain model can become richer with behavior. And if so, those entities can merit a domain model pattern.

Alternative to Anemic Domain Model

As an alternative to the anemic domain model (discussed above), the classes can be moved out of the domain logic layer and in to the presentation layer. Instead of naming it PersonRepository, it is now named PersonDao.

Class Diagram 0 Image
package com.acme.bc.interfaces.web;
 
@Entity
class Person {...}
 
@Controller
class PersonsController {
  private PersonDao personDao;
  public PersonsController(PersonDao personDao) {...}
  // ...
}
 
interface PersonDao /* extends CrudRepository<Person, Long> */ {
  // CRUD methods (e.g. find, find/pagination, update, delete)
}
package com.acme.bc.infrastructure.persistence;
 
class PersonDaoJpa implements PersonDao {
  ...
}

Too Much Layering

I think that it would be an overkill if you have to go through a mandatory application service that does not add value.

package com.acme.bc.interfaces.web;
...
@Controller
class PersonsController {
  private PersonService personService;
  public PersonsController(PersonService personService) {...}
  // ...
}
package com.acme.bc.application;
...
@Service
class PersonService {
  private PersonRepository personRepository;
  public PersonService(PersonRepository personRepository) {...}
  // expose repository CRUD methods and pass to repository
  // no value add
}

Keep the repository in the domain.model package. Place the repository implementations in another package (e.g. infrastructure.persistence). But why?

The domain.model package is where the repository is defined. The elements in the domain model dictate what methods are needed in the repository interface definition. Thus, the repository definition is placed in the domain.model package. The repository implementations need to follow what new methods are added (or remove unused ones). This packaging follows the dependency inversion principle. The infrastructure.persistence package depends on the domain.model package, and not the other way around.

Application Services for Transactions

So, when would application services be appropriate? The application services are responsible for driving workflow and coordinating transaction management (e.g. by use of the declarative transaction management support in Spring).

If you find the simple CRUD application needing to start transactions in the presentation-layer controller, then it might be a good sign to move them into an application service. This usually happens when the controller needs to update more than one entity that does not have a single root. The usual example here is transferring amounts between bank accounts. A transaction is needed to ensure that debit and credit both succeed, or both fail.

Class Diagram 2 Image
package sample.domain.model;
...
@Entity
class Account {...}
...
interface AccountRepository {...}
package sample.interfaces.web;
...
@Controller
class AccountsController {
  private AccountRepository accountRepository;
  ...
  @Transactional
  public ... transfer(...) {...}
}

If you see this, then it might be a good idea to move this (from the presentation layer) to an application-layer service.

package sample.interfaces.web;
...
@Controller
class AccountsController {
  private AccountRepository accountRepository;
  private TransferService transferService;
  ...
  public ... transfer(...) {...}
}

package sample.application;
...
@Service
@Transactional
class TransferService {
  private AccountRepository accountRepository;
  ...
  public ... transfer(...) {...}
}
package sample.domain.model;
...
@Entity
class Account {...}
...
interface AccountRepository {...}

Domain Model Pattern (only) for Complex Logic

I’ll use the double-entry accounting as an example. But I’m sure there are more complex logic that’s better suited.

Let’s say we model journal entries and accounts as domain entities. The account contains a balance (a monetary amount). But this amount is not something that one would simply set. A journal entry needs to be created. When the journal entry is posted, it will affect the specified accounts. The account will then update its balance.

package.accounting.domain.model;
...
/** Immutable */
@Entity
class JournalEntry {
  // zero-sum items
  @ElementCollection
  private Collection<JournalEntryItem> items;
  ...
}
...
/** A value object */
@Embeddable
class JournalEntryItem {...}
...
interface JournalEntryRepository {...}
...
@Entity
class Account {...}
...
interface AccountRepository {...}
...
@Entity
class AccountTransaction {...}
...
interface AccountTransactionRepository {...}

Now, in this case, a naive implementation would have a presentation-layer controller create a journal entry object, and use a repository to save it. And at some point in time (or if auto-posting is used), the corresponding account transactions are created, with account balances updated. All this needs to be rolled into a transaction (i.e. all-or-nothing).

Again, this transaction is ideally moved to an application service.

package.accounting.application;
 
@Service
@Transactional
class PostingService {...}

If there’s a need to allow the user to browse through journal entries and account transactions, the presentation-layer controller can directly use the corresponding repositories. If the domain entities are not suitable for the view technology (e.g. it doesn’t follow JavaBean naming conventions), then the presentation-layer can define DTOs that are suitable for the view. Be careful! Don’t change the domain entity just to suit the needs of the presentation-layer.

package.interfaces.web;
 
@Controller
class AccountsController {
  private AccountRepository accountRepository;
  private AccountTransactionRepository accountTransactionRepository;
  private PostingService postingService;
  ...
}

In Closing…

So, there you have it. Hopefully, this post can shed some light on when (and when not) to use domain model pattern.

Class Diagram 3 Image

Now I think I need a cold one.

Alchohol Drinks
Image by Nicolas Postiglioni via Pexels

Originally posted at: Architectural Layers and Modeling Domain Logic

Internship 2023 Image Banner

Intern @ O&B

GROW WITH US AND
MAKE A DIFFERENCE

Apply for Internship
Internship 2023 Image Banner

Intern @ O&B

GROW WITH US AND
MAKE A DIFFERENCE

Apply for Internship