Principled way to design and implement functional domain models using some of the patterns of domain driven design. DDD, as the name suggests, is focused towards the domain model and the patterns of architecture that it encourages are also based on how we think of interactions amongst the basic abstractions of the domain. Of course the primary goal of the talk is to discuss how Scala and Zio 2 can be a potent combination in realizing the implementation of such models. This is not a talk on FP, the focus will be on how to structure and modularise an application based on some of the patterns of DDD.
2. What is a domain model ?
A domain model in problem solving and software engineering is a
conceptual model of all the topics related to a speci
fi
c problem. It
describes the various entities, their attributes, roles, and relationships,
plus the constraints that govern the problem domain. It does not
describe the solutions to the problem.
Wikipedia (http://en.wikipedia.org/wiki/Domain_model)
8. • Nouns in the model
• Pure objects
• Names consistent with domain vocabulary
• Strongly typed (newtypes, refinement types)
9. • Provides object storage and access
• Abstractions with Effects
• Names consistent with the aggregate root that gets stored
• Presents clients with simple interfaces to manage persistence
• Decouples model design from persistence technology
10. • Provides object storage and access
• Abstractions with Effects
• Names consistent with the aggregate root that gets stored
• Presents clients with simple interfaces to manage persistence
• Decouples model design from persistence technology
Client code ignores repository implementation, developers do not
- Eric Evans in the DDD blue book
11. • Coarse grained abstractions
• Relates to domain concept not specific to a single entity
• Interface defined in terms of other elements of the model
• Abstractions with Effects
13. final case class Account private (
no: AccountNo,
name: AccountName,
dateOfOpen: ZonedDateTime,
//..
)
Model definition of an entity as an algebraic data type
• Types with names derived from domain
vocabulary
• Rich semantics with newtypes,
re
fi
nement types etc. from zio-prelude
14. final case class Account private (
no: AccountNo,
name: AccountName,
dateOfOpen: ZonedDateTime,
//..
)
object Account {
def tradingAccount(
no: String,
name: String,
openDate: Option[ZonedDateTime],
//..
): Validation[String, Account] = { //..
private[model] def validateAccountNo(
no: String): Validation[String, AccountNo] = //..
//..
def close(
a: Account,
closeDate: ZonedDateTime
): Validation[String, Account] = { //..
}
Model definition of an entity as an algebraic data type A Module for the model containing definitions for
smart constructors, validations, and other domain
logic for the model
• Types with names derived from domain
vocabulary
• Rich semantics with newtypes,
re
fi
nement types etc. from zio-prelude
• Abstractions that compose
• Build larger abstractions - validations
for entire domain entity as a
composition of smaller validations
• Supported by zio-prelude
• Smart constructor
• Domain behaviour
15. final case class Account private (
no: AccountNo,
name: AccountName,
dateOfOpen: ZonedDateTime,
//..
)
object Account {
def tradingAccount(
no: String,
name: String,
openDate: Option[ZonedDateTime],
//..
): Validation[String, Account] = { //..
private[model] def validateAccountNo(
no: String): Validation[String, AccountNo] = //..
//..
def close(
a: Account,
closeDate: ZonedDateTime
): Validation[String, Account] = { //..
}
Model definition of an entity as an algebraic data type A Module for the model containing definitions for
smart constructors, validations, and other domain
logic for the model
• Types with names derived from domain
vocabulary
• Rich semantics with newtypes,
re
fi
nement types etc. from zio-prelude
• Abstractions that compose
• Build larger abstractions - validations
for entire domain entity as a
composition of smaller validations
• Supported by zio-prelude
• Smart constructor
• Domain behaviour
•Pure model
•Functional core
•Compositional with zio-prelude
16. Repository
trait AccountRepository {
/** query by account number */
def queryByAccountNo(no: AccountNo): Task[Option[Account]]
/** store */
def store(a: Account): Task[Account]
/** store many */
def store(as: List[Account]): Task[Unit]
/** query by opened date */
def allOpenedOn(openedOnDate: LocalDate): Task[List[Account]]
//..
}
17. Repository
trait AccountRepository {
/** query by account number */
def queryByAccountNo(no: AccountNo): Task[Option[Account]]
/** store */
def store(a: Account): Task[Account]
/** store many */
def store(as: List[Account]): Task[Unit]
/** query by opened date */
def allOpenedOn(openedOnDate: LocalDate): Task[List[Account]]
//..
}
• Effectful contract
• Concrete ZIO effect (unlike tagless
fi
nal)
• Less parametric than the tagless
fi
nal approach
• No dependency on any speci
fi
c environment
• zio.Task[A] => ZIO[Any,Throwable,A]
19. Repository - Implementations
final case class AccountRepositoryLive(
xaResource: Resource[Task, Transactor[Task]])
extends AccountRepository {
import AccountRepositoryLive.SQL
def all: Task[List[Account]] =
xaResource.use { xa =>
SQL.getAll
.to[List]
.transact(xa)
.orDie
}
def queryByAccountNo(no: AccountNo): Task[Option[Account]] =
xaResource.use { xa =>
SQL
.get(no.value.value)
.option
.transact(xa)
.orDie
}
//..
}
object AccountRepositoryLive extends CatzInterop {
val layer: ZLayer[DBConfig, Throwable, AccountRepository] = {
ZLayer
.scoped(for {
cfg <- ZIO.service[DBConfig]
transactor <- mkTransactor(cfg)
} yield new AccountRepositoryLive(transactor))
}
//..
}
• Implementation
details
• Functional core
• ZLayer as a recipe for creating ZIO services from dependencies
• ZLayer serves as the factory for creating domain artifacts
• The scoped API ensures that resource lifetimes are properly
managed
• Inject the proper layer as dependency
20. REPOSITORIES have many advantages, including the following:
• They present clients with a simple model for obtaining persistent
objects and managing their life cycle.
• They decouple application and domain design from persistence
technology, multiple database strategies, or even multiple data
sources
• They communicate design decisions about object access
• They allow easy substitution of dummy implementation, for use in
testing (typically using an in-memory allocation)
- Eric Evans (The Blue DDD book)
21. REPOSITORIES have many advantages, including the following:
• They present clients with a simple model for obtaining persistent
objects and managing their life cycle.
• They decouple application and domain design from persistence
technology, multiple database strategies, or even multiple data
sources
• They communicate design decisions about object access
• They allow easy substitution of dummy implementation, for use in
testing (typically using an in-memory allocation)
- Eric Evans (The Blue DDD book)
Single interface principle with return types
that abstract the value as well as handling of
failures through zio effects
22. REPOSITORIES have many advantages, including the following:
• They present clients with a simple model for obtaining persistent
objects and managing their life cycle.
• They decouple application and domain design from persistence
technology, multiple database strategies, or even multiple data
sources
• They communicate design decisions about object access
• They allow easy substitution of dummy implementation, for use in
testing (typically using an in-memory allocation)
- Eric Evans (The Blue DDD book)
Single interface principle with return types
that abstract the value as well as handling of
failures through zio effects
Allow multiple implementations (the OO way)
that can be injected dynamically and
compositionally through ZLayers
23. REPOSITORIES have many advantages, including the following:
• They present clients with a simple model for obtaining persistent
objects and managing their life cycle.
• They decouple application and domain design from persistence
technology, multiple database strategies, or even multiple data
sources
• They communicate design decisions about object access
• They allow easy substitution of dummy implementation, for use in
testing (typically using an in-memory allocation)
- Eric Evans (The Blue DDD book)
Single interface principle with return types
that abstract the value as well as handling of
failures through zio effects
Allow multiple implementations (the OO way)
that can be injected dynamically and
compositionally through ZLayers
The concrete zio effect that gets returned
clearly indicates semantics of failure handling,
environment dependencies (if any) and the
exception types
24. REPOSITORIES have many advantages, including the following:
• They present clients with a simple model for obtaining persistent
objects and managing their life cycle.
• They decouple application and domain design from persistence
technology, multiple database strategies, or even multiple data
sources
• They communicate design decisions about object access
• They allow easy substitution of dummy implementation, for use in
testing (typically using an in-memory allocation)
- Eric Evans (The Blue DDD book)
Single interface principle with return types
that abstract the value as well as handling of
failures through zio effects
Allow multiple implementations (the OO way)
that can be injected dynamically and
compositionally through ZLayers
The concrete zio effect that gets returned
clearly indicates semantics of failure handling,
environment dependencies (if any) and the
exception types
ZLayers! ZLayers!
25.
26. Services
trait TradingService {
def getAccountsOpenedOn(openDate: LocalDate): IO[TradingError, List[Account]]
def getTrades(forAccountNo: AccountNo,
forDate: Option[LocalDate] = None
): IO[TradingError, List[Trade]]
def getTradesByISINCodes(forDate: LocalDate,
forIsins: Set[model.instrument.ISINCode]
): IO[TradingError, List[Trade]]
def orders(frontOfficeOrders: NonEmptyList[FrontOfficeOrder]
): IO[TradingError, NonEmptyList[Order]]
def execute(orders: NonEmptyList[Order],
market: Market,
brokerAccountNo: AccountNo
): IO[TradingError, NonEmptyList[Execution]]
def allocate(executions: NonEmptyList[Execution],
clientAccounts: NonEmptyList[AccountNo],
userId: UserId
): IO[TradingError, NonEmptyList[Trade]]
}
• Effectful contracts
• Concrete ZIO effect (unlike tagless
fi
nal) - clearly publishes
all exceptions and returned value types
• Less parametric than the tagless
fi
nal approach
• No dependency on any speci
fi
c environment
• zio.IO[A] => ZIO[Any, E, A]
• Coarse grained abstractions - not speci
fi
c to any entity
• All names derived from domain vocabulary
28. Services
trait TradingService {
def getAccountsOpenedOn(openDate: LocalDate): IO[TradingError, List[Account]]
def getTrades(forAccountNo: AccountNo,
forDate: Option[LocalDate] = None
): IO[TradingError, List[Trade]]
def getTradesByISINCodes(forDate: LocalDate,
forIsins: Set[model.instrument.ISINCode]
): IO[TradingError, List[Trade]]
def orders(frontOfficeOrders: NonEmptyList[FrontOfficeOrder]
): IO[TradingError, NonEmptyList[Order]]
def execute(orders: NonEmptyList[Order],
market: Market,
brokerAccountNo: AccountNo
): IO[TradingError, NonEmptyList[Execution]]
def allocate(executions: NonEmptyList[Execution],
clientAccounts: NonEmptyList[AccountNo],
userId: UserId
): IO[TradingError, NonEmptyList[Trade]]
}
• Effectful contracts
• Concrete ZIO effect (unlike tagless
fi
nal) - clearly publishes
all exceptions and returned value types
• Less parametric than the tagless
fi
nal approach
• No dependency on any speci
fi
c environment
• zio.IO[A] => ZIO[Any, E, A]
• Coarse grained abstractions - not speci
fi
c to any entity
• All names derived from domain vocabulary
30. Services - Implementation
final case class TradingServiceLive(
ar: AccountRepository,
or: OrderRepository,
er: ExecutionRepository,
tr: TradeRepository
) extends TradingService { //..
object TradingServiceLive {
val layer: ZLayer[
AccountRepository with OrderRepository with
ExecutionRepository with TradeRepository,
Nothing,
TradingServiceLive
] = ZLayer.fromFunction(TradingServiceLive.apply _)
}
• Dependencies passed as constructor arguments
• And automatically becomes part of the ZLayer and hence
part of the dependency graph
• Dependencies are interfaces only and not implementations
33. Wiring up ..
ZIO
.serviceWithZIO[TradingService](service => //..)
.provide(
AccountRepositoryLive.layer,
TradingServiceLive.layer,
OrderRepositoryLive.layer,
TradeRepositoryLive.layer,
ExecutionRepositoryLive.layer,
config.live
)
• Declarative way to build an instance of your domain
service from the speci
fi
ed dependencies
• ZLayers
- Allow automatic construction of the dependency
graph of your domain model
- Are composable
- Are asynchronous and non-blocking
- Can be acquired in parallel unlike class constructors
35. Entities
Value Objects
<<pure>> Repositories
<<interfaces>>
Implementations
• pure
• functional
• statically typed
• use zio-prelude for
newtypes,
re
fi
nement types,
validations etc.
• plain old scala traits
• one per aggregate root
• effectual contracts
• concrete zio effects
• persistence platform dependent
• pass dependencies as constructor arguments
• can be multiple for a single contract
36. Entities
Value Objects
<<pure>> Repositories
<<interfaces>>
Implementations
Domain
Services
Domain
Service
Implementations
<<interfaces>>
• pure
• functional
• statically typed
• use zio-prelude for
newtypes,
re
fi
nement types,
validations etc.
• plain old scala traits
• one per aggregate root
• effectual contracts
• concrete zio effects
• persistence platform dependent
• pass dependencies as constructor arguments
• can be multiple for a single contract
• coarse grained
• can interact with multiple domain entities
• effectual contracts
• concrete zio effects
• pass dependencies as constructor
arguments
• depends only on repository interfaces and
NOT implementations
37. Entities
Value Objects
<<pure>> Repositories
<<interfaces>>
Implementations
Domain
Services
Domain
Service
Implementations
<<interfaces>>
Application
• pure
• functional
• statically typed
• use zio-prelude for
newtypes,
re
fi
nement types,
validations etc.
• plain old scala traits
• one per aggregate root
• effectual contracts
• concrete zio effects
• persistence platform dependent
• pass dependencies as constructor arguments
• can be multiple for a single contract
• coarse grained
• can interact with multiple domain entities
• effectual contracts
• concrete zio effects
• pass dependencies as constructor
arguments
• depends only on repository interfaces and
NOT implementations
• ZLayer magic!
38. Entities
Value Objects
<<pure>> Repositories
<<interfaces>>
Implementations
Domain
Services
Domain
Service
Implementations
<<interfaces>>
Application
• pure
• functional
• statically typed
• use zio-prelude for
newtypes,
re
fi
nement types,
validations etc.
• plain old scala traits
• one per aggregate root
• effectual contracts
• concrete zio effects
• persistence platform dependent
• pass dependencies as constructor arguments
• can be multiple for a single contract
• coarse grained
• can interact with multiple domain entities
• effectual contracts
• concrete zio effects
• pass dependencies as constructor
arguments
• depends only on repository interfaces and
NOT implementations
• ZLayer magic!