Every entity must be created, but how do we implement this? Which component decides when to create an entity? Which component contains the creation logic? In this post, we will explore various options, highlight their pros and cons, and hopefully come up with a guideline for future decisions.

For this study I will use the following model


case class Post(id: PostId, content: PostContent, createdAt: Instant)

I consider the generation of PostId and obtaining the current Instant to be an effect.

I consider determinism important for the sake of testability. Please see the pragmatic notes in each case to see how you can simplify things, if you don’t do unit testing and prefer integration testing.

I consider aggregate ids to be a persistence concern, so it should not be generated within the domain objects.

The order of options corresponds to how far we push the concerns away from the very core of our system, i.e. domain.

Untitled

  1. Domain layer
    1. Entity companion object
    2. Entity factory
    3. Parent entity
  2. Application layer
  3. Infrastructure layer

Let’s introduce an alias type for out PostId generator

type PostIdGen = UIO[PostId]

In production wiring we will just use PostId.generate method.

Entity Companion Object

The idea is to put creation logic into companion object method. I inject the generator as one of the method’s parameters and Clock is provided by the ZIO runtime.

object Post {
	def create(generateId: PostIdGen, content: PostContent): UIO[Post] =
		for {
			id <- generateId
			now <- zio.Clock.instant
		} yield new Post(_, content, now) 		
}

And our application service

class PostManagementService(generateId: PostIdGen, repo: PostRepository) {
  def publishPost(content: PostContent): UIO[PostId] =
		for {
			post <- Post.create(generateId, content)
			_ <- repo.save(post)
		} yield post.id
}