View on GitHub

Typed Use Case

Use case definition, for which this console application can generate PlantUML diagram, where all services are domain specific type safe.

Domain

Home / Domain


Domain is a DDD model, written in the F# as .fsx (F# script).

It should only contain Types (at least for now).

Table of contents

How does it work?

It uses a Fsharp.Compiler.Service under the hood.

Domain types

You can use whatever type you want, but abbreviation.

Abbreviation is not supported, since the compiler just replace the Abbreviation with the real type, it doesn’t mean anything in the Domain.

type Id = string    // this is abbreviation for string

type Id = Id of string  // this is correct

// or use just an Id, if you don't want to specify a concrete type
type Id = Id
type Event = Event // this is ok

type SomethingCreatedEvent = Event   // this is abbreviation for Event

type SomethingCreatedEvent = SomethingCreatedEvent of Event // this is correct

Common types

Common types helps a Resolver to resolve a type with specific purpose or usage.

Initiator

Initiator is the main service, which occurs in the use-case.

type MyMainService = Initiator

Data Object

Data object is defined as a list of Data.

It allows you to post data into the data object and read data from data object.

type DataObject<'Data> = DataObject of 'Data list

Data Objects are used as a type of your concrete data object.

For example, when you have a database of Persons, it might be as following:

type Database<'Entity> = Database of 'Entity list

type PersonDatabase = PersonDatabase of Database<Person>

and Person = {
    Name: string
}

Stream

Stream is defined as a list of Events. Stream type is one of a specific Data Objects.

It allows you to post events into the stream, read events from stream or handle an event in stream.

type Stream<'Event> = Stream of 'Event list

Stream are used as a type of your concrete stream.

For example, when you have a stream of Interactions, it might be as following:

type InteractionStream = InteractionStream of Stream<InteractionEvent>

and InteractionEvent =
    | Confirmed
    | Rejected

Handler

Handler is a generic function, which handles a data.

It must be a generic type with exactly one generic parameter and it must have a Handler suffix.

Handlers allow you to handle a Data from DataObjects, by a special tuc syntax.

type Handler<'Data> = Handler of ('Data -> unit)

So for example you can have a StreamHandler, which would handle an Event in a Stream.

type StreamHandler<'Event> = StreamHandler of ('Event -> unit)

Handler is just a function, so you need a Service, to contain such handler.

type StreamListenerService = {
    OnInteractionEvent = StreamHandler<InteractionEvent>
}

In StreamListenerService record, there now would be a Handler OnInteractionEvent, which can handle an InteractionEvent from a Stream of such event.

Domain example

// Common types

type Id = UUID

type Stream<'Event> = Stream of 'Event list
type StreamHandler<'Event> = StreamHandler of ('Event -> unit)

// Types

type InteractionEvent =
    | Confirmation
    | Rejection

type InteractionResult =
    | Accepted
    | Error

type IdentityMatchingSet = {
    Contact: Contact
}

and Contact = {
    Email: Email option
    Phone: Phone option
}

and Email = Email of string
and Phone = Phone of string

type Person =
    | Known of PersonId
    | Unknown

and PersonId = PersonId of Id

// Streams

type InteractionCollectorStream = InteractionCollectorStream of Stream<InteractionEvent>

// Services

type GenericService = Initiator

type InteractionCollector = {
    PostInteraction: InteractionEvent -> InteractionResult
}

type PersonIdentificationEngine = {
    OnInteractionEvent: InteractionEvent -> unit
}

type PersonAggregate = {
    IdentifyPerson: IdentityMatchingSet -> Person
}

Share types between domains

There is often a situation, where you need to share some types between multiple domains (DTOs, Common Types, …).

You can use a #load key word in .fsx file.

TIP: You should avoid loading one domain from another, since domains should have its boundaries.

// common.fsx

type Id = UUID

type Stream<'Event> = Stream of 'Event list
type StreamHandler<'Event> = StreamHandler of ('Event -> unit)
// cars-persons-shared.fsx

#load "common.fsx"
open Common

type CarDto = {
    Id: Id
    Name: string
    Type: string
}

type PersonDto = {
    Id: Id
    Name: string
    Address: string
}
// carsDomain.fsx

#load "cars-persons-share.fsx"
open ``Cars-persons-share``

open Common

type CarFinder = {
    FindCar: Id -> CarDto
}
// personsDomain.fsx

#load "cars-persons-share.fsx"
open ``Cars-persons-share``

open Common

type PersonEvent =
    | BoughtCar of PersonBoughtCarEvent

and PersonBoughtCarEvent = {
    PersonId: Id
    Car: CarDto
}

type PersonStream = PersonStream of Stream<PersonEvent>