diff --git a/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala b/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala index 2bc42b4..65c7e92 100644 --- a/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala +++ b/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala @@ -13,7 +13,7 @@ * @tparam State * Aggregate root state */ -trait AggregateRoot[Id, Command, Event, State, Error <: AggregateError]: +trait AggregateRoot[Id, Error <: AggregateError, Command, Event, State]: /** Aggregate root id */ def id: Id diff --git a/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala b/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala index 2bc42b4..65c7e92 100644 --- a/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala +++ b/core/shared/src/main/scala/works/iterative/entity/AggregateRoot.scala @@ -13,7 +13,7 @@ * @tparam State * Aggregate root state */ -trait AggregateRoot[Id, Command, Event, State, Error <: AggregateError]: +trait AggregateRoot[Id, Error <: AggregateError, Command, Event, State]: /** Aggregate root id */ def id: Id diff --git a/core/shared/src/main/scala/works/iterative/entity/AggregateRootFactory.scala b/core/shared/src/main/scala/works/iterative/entity/AggregateRootFactory.scala new file mode 100644 index 0000000..c051650 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/entity/AggregateRootFactory.scala @@ -0,0 +1,58 @@ +package works.iterative.entity + +import zio.* +import works.iterative.core.UserMessage +import works.iterative.core.service.IdGenerator + +sealed trait FactoryError: + def entityId: String + def userMessage: UserMessage + +object FactoryError: + case class EntityAlreadyExists[Id](entityId: String, id: Id) + extends FactoryError: + def userMessage = UserMessage(s"${entityId}.error.entity.exists", id) + + case class EntityNotFound[Id](entityId: String, id: Id) extends FactoryError: + def userMessage = UserMessage(s"${entityId}.error.entity.not.found", id) + +trait AggregateRootFactory[ + Ident, + T <: AggregateRoot[Ident, ?, ?, ?, ?] +]: + type Id = Ident + type NotFound = FactoryError.EntityNotFound[Id] + type AlreadyExists = FactoryError.EntityAlreadyExists[Id] + + protected def AlreadyExists(id: Id): AlreadyExists = + FactoryError.EntityAlreadyExists(entityId, id) + + protected def NotFound(id: Id): NotFound = + FactoryError.EntityNotFound(entityId, id) + + def entityId: String + def make(id: Id): IO[AlreadyExists, T] + def load(id: Id): IO[NotFound, T] + + def loadOrMake(id: Id): UIO[T] = + load(id) + .orElse(make(id)) + .orDieWith(_ => + new RuntimeException( + s"Loading or making entity ${entityId} with id ${id} failed, loading fails with NotFound, making fails with AlreadyExists" + ) + ) + + def make()(using idGen: IdGenerator[Id]): UIO[T] = + val tryToCreate = for + id <- idGen.nextId + entity <- make(id) + yield entity + + tryToCreate + .retryN(10) + .orDieWith(_ => + new RuntimeException( + "Cannot create entity, generator failed 10 times to create new ID" + ) + )