DEV Community

loading...
Cover image for A Type of Happiness Inc.

A Type of Happiness Inc.

raimeyuu profile image Damian Płaza ・11 min read

Prologue

Once upon a time, an elf nameSanta knows his business, isn't hed Harry joined A Type of Happiness Inc. that helped Santa to gather children's wishes and prepare gifts based on the letters.

Everything was manual, laborious, and not easy to wrap an elvish head around - warehouses, packages, colorful ribbons, and tons of glitter.

Fortunately for the company, Harry spoke not only elvish language but also he knew F#. His manager took him to Santa and told a story about how using software might revolutionize the entire business. The old man didn't hesitate, because he was tired of all the paper letters coming to the headquarters. "You need to keep it for the next five years in the guarded warehouse because that's what the law states".

What if I told you, we can be (f)sharp

Harry thought he will create multi Kubernetes cluster microservices architecture, driven by events, powered by Kafka, and many other crucial pieces to run a business in 2020. Before jumping into the data lakes, he decided to start with the most important thing - the domain.

He had multiple Santa Storming sessions and he transferred as much knowledge as he could cognitively acquire at that moment.

"Children have wishes, they are getting gifts. Some children might have impolite behavior that needs to be verified." - Santa said.

module ChristmasDomain =
        type Gift = Gift of string

        type Wish = Wish of string

        type ImpoliteBehavior = ImpoliteBehavior of string 
Enter fullscreen mode Exit fullscreen mode

Harry was typing quickly while listening carefully.

"Yesterday on Hacker News I saw an interesting article about some kind of machinery learning that might be really helpful. Along with a child wish, maybe it could suggest some other wishes." - Santa spoke with a spark in his eyes.

Harry said to himself "I thought children know best what they would like to get, but well, Santa knows his business, doesn't he?". He updated his precious code with the following:

module ChristmasDomain =
    open System
        type Gift = Gift of string

        type Wish = Wish of string

        type ImpoliteBehavior = ImpoliteBehavior of string

        type MachineLearningWish = {
            Accuracy: float
            Precision: float
            Wish: Wish
        }
Enter fullscreen mode Exit fullscreen mode

"We need to create a gift preparation job. This gets tracked with its unique identifier. We need to know when it was created. Then we need to verify a child's behavior against The Great Registry of Children Behavior." - Santa started describing how the main process looks like.

Harry suggested that wishes might be sent as HTTP requests by some fancy device sold separately to the shops. Santa said that for him it's just a detail of how they will get all the wishes. But he approved the idea and said that now that's the moment when all the machinery learning should kick in, giving the most trendy wishes among all the children. Harry started building his mental model precisely.

"Typically, now elves recommend the gift for a kid, but having this learning thingy, we need to require both types of wishes." - Santa said contemplatively while thinking how the heck this machinery works.

Harry thought about Status type describing all possible cases that every fellow fsharper would do. In his imagination, he saw all those ifs and match expressions and begun sweating a little bit.

"That's all", Santa said. "Based on the elvish decision if there is a recommended gift or not, this job should be completed and your work is over. I will print all completed jobs on paper and do my work." - Santa continued.

Harry tried to elaborate on why printing all completed jobs isn't the best idea and how they could utilize a reactive stream on one of his Kafka topics to send a completed job on his email, but Santa didn't listen. He just wanted to get his work done.

Let's start objectively

Our (f)sharp elf continued working hard on his domain model:

type Status = 
            private 
            | Created
            | VerifiedKidPolite
            | VerifiedKidImpolite
            | WithMachineLearningWishes
            | WithChildWish
            | WithCombinedWishes
            | WithRecommendedGift
            | CompletedJob
Enter fullscreen mode Exit fullscreen mode

He expressed all possible scenarios Santa mentioned during one of their collaborative Santa Storming events. It looked really good.

Happiness made easy

Harry continued exploring the domain through his fingers. He experienced an almost mythical, god-like feeling while he was transferring ideas into a bunch of characters displayed in his favorite IDE with Ionide installed, dark mode turned on:

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
Enter fullscreen mode Exit fullscreen mode

"That's what Dependency Injection is baby!" - thought Harry.

Then he started encoding the first part of the process - verification whether a child was behaving politely or not.

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite
Enter fullscreen mode Exit fullscreen mode

"Backward pipe operator. I knew I could use it somewhere." - Harry was feeling so smart when we observed this thought.

He fulfilled the requirements partially. There should be also a possibility to give Santa information about a child who was behaving impolitely.

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None
            let mutable impoliteBehavior: ImpoliteBehavior array = Array.empty<ImpoliteBehavior>

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite

            member this.VerifyIncorrect(impoliteBehaviors': ImpoliteBehavior array) = 
                impoliteBehavior <- impoliteBehaviors'
                status <- VerifiedKidImpolite
Enter fullscreen mode Exit fullscreen mode

Bring your wishlist

"What's next on our list?" - Harry asked himself in his mind as if someone was sitting next to him. The most important part of the process was missing - tracking the wishes.

The friendly (f)sharp elf continued his internal dialogue between him and himself - "What is the purpose of life if you don't have big dreams?".

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None
            let mutable impoliteBehavior: ImpoliteBehavior array = Array.empty<ImpoliteBehavior>
            let mutable childWish: Wish option = None

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite

            member this.VerifyIncorrect(impoliteBehaviors': ImpoliteBehavior array) = 
                impoliteBehavior <- impoliteBehaviors'
                status <- VerifiedKidImpolite

            member this.AddChildWish(wish: Wish) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add a child wish to the job when child was impolite"
                    else 
                        if status = WithMachineLearningWishes
                            then status <- WithCombinedWishes
                            else status <- WithChildWish
                        childWish <- Some wish
Enter fullscreen mode Exit fullscreen mode

This looked correct. Whenever a child wasn't polite, a job cannot be further processed. Harry took into account state with cool machine learning wishes. He impatiently looked forward to working with all the great and so massively interesting new pieces of related technology.

Wishes Recommendation Engine

Is there anything more precious than children's dreams? "Yes, there is" - said Harry swimming in his thoughts with all the monumental technological components he can potentially work with. "AI-powered wishlist generation, online learning engine that uses a multi-agent reinforcement learning tool, producing wishes with high accuracy, based on all the good deeds of the given child". Harry almost started crying but then he realized he has some code to write!

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None
            let mutable impoliteBehavior: ImpoliteBehavior array = Array.empty<ImpoliteBehavior>
            let mutable childWish: Wish option = None
            let mutable machineLearningWishes = Array.empty<MachineLearningWish>

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite

            member this.VerifyIncorrect(impoliteBehaviors': ImpoliteBehavior array) = 
                impoliteBehavior <- impoliteBehaviors'
                status <- VerifiedKidImpolite

            member this.AddChildWish(wish: Wish) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add a child wish to the job when child was impolite"
                    else 
                        if status = WithMachineLearningWishes
                            then status <- WithCombinedWishes
                            else status <- WishChildWish
                        childWish <- Some wish

            member this.AddMachineLearningWish(wishes: MachineLearningWish array) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add ML wishes to a job when child was impolite"
                    else 
                        if status = WishChildWish
                            then status <- WithCombinedWishes
                            else status <- WithMachineLearningWishes
                        machineLearningWishes <- wishes
Enter fullscreen mode Exit fullscreen mode

A dream come true

It looked like Harry handled almost all the parts of the gift preparation process! Now the funniest part - gifts. Who doesn't like them?

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None
            let mutable impoliteBehavior: ImpoliteBehavior array = Array.empty<ImpoliteBehavior>
            let mutable childWish: Wish option = None
            let mutable machineLearningWishes = Array.empty<MachineLearningWish>
            let mutable recommendedGift: Gift option = None

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite

            member this.VerifyIncorrect(impoliteBehaviors': ImpoliteBehavior array) = 
                impoliteBehavior <- impoliteBehaviors'
                status <- VerifiedKidImpolite

            member this.AddChildWish(wish: Wish) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add a child wish to the job when child was impolite"
                    else 
                        if status = WithMachineLearningWishes
                            then status <- WithCombinedWishes
                            else status <- WishChildWish
                        childWish <- Some wish

            member this.AddMachineLearningWish(wishes: MachineLearningWish array) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add ML wishes to a job when child was impolite"
                    else 
                        if status = WishChildWish
                            then status <- WithCombinedWishes
                            else status <- WithMachineLearningWishes
                        machineLearningWishes <- wishes

            member this.RecommendGift(gift: Gift) =
                if status = WithCombinedWishes 
                    then 
                        recommendedGift <- Some gift
                        status <- WithRecommendedGift
                    else failwith "Cannot recommend gift when one of the type of wishes is missing"
Enter fullscreen mode Exit fullscreen mode

All the datasets flowing through the Kubernetes processing pipelines, petabytes of wishlists, toys, and hidden dreams of every child believing in Santa - Harry couldn't wait to write all those yaml files, oh boy! "But let's quickly finish the domain part" he thought.

Elvish job done, Santa's job starts

Finally, the last piece of the puzzle! Only one step was stopping Harry from working with all the HTTP handlers, controllers, databases, queues, and other interesting stuff. He recalled what Santa said about the job completeness - only when there are both types of wishes or when there is a recommended gift.

        type GiftPreparationJob(now: unit -> DateTime, guid: unit -> Guid) =
            let mutable status: Status = Created
            let id: Guid = guid()
            let createdAt: DateTime = now()
            let mutable verifiedAt: DateTime option = None
            let mutable impoliteBehavior: ImpoliteBehavior array = Array.empty<ImpoliteBehavior>
            let mutable childWish: Wish option = None
            let mutable machineLearningWishes = Array.empty<MachineLearningWish>
            let mutable recommendedGift: Gift option = None

            member this.VerifyCorrect() = 
                verifiedAt <- Some <| now()
                status <- VerifiedKidPolite

            member this.VerifyIncorrect(impoliteBehaviors': ImpoliteBehavior array) = 
                impoliteBehavior <- impoliteBehaviors'
                status <- VerifiedKidImpolite

            member this.AddChildWish(wish: Wish) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add a child wish to the job when child was impolite"
                    else 
                        if status = WithMachineLearningWishes
                            then status <- WithCombinedWishes
                            else status <- WishChildWish
                        childWish <- Some wish

            member this.AddMachineLearningWish(wishes: MachineLearningWish array) =
                if status = VerifiedKidImpolite
                    then failwith "Cannot add ML wishes to a job when child was impolite"
                    else 
                        if status = WishChildWish
                            then status <- WithCombinedWishes
                            else status <- WithMachineLearningWishes
                        machineLearningWishes <- wishes

            member this.RecommendGift(gift: Gift) =
                if status = WithCombinedWishes 
                    then 
                        recommendedGift <- Some gift
                        status <- WithRecommendedGift
                    else failwith "Cannot recommend gift when one of the type of wishes is missing"

            member this.Complete() = 
                if status = WithCombinedWishes or status = WithRecommendedGift
                    then status <- CompletedJob
                    else failwith "Cannot complete job without a recommended gift or without both type of wishes"
Enter fullscreen mode Exit fullscreen mode

Looks like he nailed it. "Kafka, now" - Harry smiled.

Reality hits elves hard

The next day was full of meetings on how to deploy Harry's multi-cluster architecture to various regions taking into account important decisions that each engineer needs to make. Harry was so busy attending to the meetings so he tried to teach another friendly elf, Barry, how to proceed with the code he wrote.

Turned out that for Harry it was a day full of meeting, but not a day full of glory. Barry was from the warehousing department and didn't know about the gift preparation process so he misused all the methods exposed. He got a bunch of exceptions here and there, for example not knowing that he cannot recommend a gift if there are no wishes for a given job!

Elves do mistakes, too

Harry and Barry thought that there must be some other way of enchanting the domain into the code so that future elvish newcomers won't be able to break anything, or at least it will click for them relatively quickly.

Both elves agreed that Harry's idea of starting with all possible cases was good enough, but maybe they could prevent impossible scenarios. They started describing the process from the beginning.

New start, new hope

Our friendly elves described the first step in the process:

module GiftPreparationJob =
            let create (now: unit -> DateTime, guid: unit -> Guid): Created = 
{ CreatedAt = now(); Id = guid() }
Enter fullscreen mode Exit fullscreen mode

"Easy peasy" - Barry thought. Compiler warned fellow elves with red squiggly lines that they are missing some types. Barry satisfied digital friend with the following code:

type Created = private {
            Id: Guid
            CreatedAt: DateTime
        }
Enter fullscreen mode Exit fullscreen mode

"Great Barry! Now let's verify if the child was polite or not." Harry suggested.

let verifyPolite (job: Created): VerifiedKidPolite = 
                { CreatedAt = job.CreatedAt; VerifiedAt = DateTime.UtcNow; Id = job.Id }

let verifyImpolite (impoliteBehaviors: ImpoliteBehavior array) (job: Created): VerifiedKidImpolite = 
                { CreatedAt = job.CreatedAt; VerifiedAt = DateTime.UtcNow; Id = job.Id; ImpoliteBehaviors = impoliteBehaviors }
Enter fullscreen mode Exit fullscreen mode

Again, the mighty compiler threw errors reminding missing types. Elves can type really quickly and moments after each side of the keyboard was satisfied:

type VerifiedKidPolite = private {
            Id: Guid
            CreatedAt: DateTime
            VerifiedAt: DateTime
        }

        type VerifiedKidImpolite = private {
            Id: Guid
            CreatedAt: DateTime
            VerifiedAt: DateTime
            ImpoliteBehaviors: ImpoliteBehavior array
        }
Enter fullscreen mode Exit fullscreen mode

A type of wish

As Harry did previously, elves started with the child's wish part of the process. They thought about how to represent that child wish might be added to either the verified kid's politeness state or to state with machine learning wishes already added. Without any hesitation, they decided to use the least resistance approach and chose Choice type. Soon they landed with such process description:

let withChildWish (childWish: Wish) (job: Choice<VerifiedKidPolite, MachineLearningWishes>): Choice<ChildWish, WishesCombined> =
                match job with
                | Choice1Of2 verifiedCorrectJob -> Choice1Of2 { ChildWish = childWish; Id = verifiedCorrectJob.Id }
                | Choice2Of2 machineLearningResults -> Choice2Of2 { MachineLearningWishes = machineLearningResults.Wishes; ChildWish = childWish; Id = machineLearningResults.Id }
Enter fullscreen mode Exit fullscreen mode

Now the compiler got really angry. MachineLearningWishes, ChildWish, and WishesCombined - those types were missing, at least that's what the compiler was suggesting.

Barry got tired so they decided that Harry will pick up where he left. Harry immediately started filling the gaps so that all red squiggles disappear.

type MachineLearningWishes = private {
            Id: Guid
            Wishes: MachineLearningWish array
        }

        type ChildWish = private {
            Id: Guid
            ChildWish: Wish
        }

        type private Wishes = {
            MachineLearningWishes: MachineLearningWish array
            ChildWish: Wish
        }

        type WishesCombined = private {
            Id: Guid
            Wishes: Wishes
        }
Enter fullscreen mode Exit fullscreen mode

Tranquility. The mighty compiler is silent, meaning they could move forward. What almost naturally emerged was another part of the process. Harry noticed that it's so close to fulfilling another Santa's requirement - machine learning generated wishes.

            let withMachineLearningWishes (job: Choice<VerifiedKidPolite, ChildWish>) (wishes: MachineLearningWish array): Choice<WishesCombined, MachineLearningWishes> =
                match job with
                | Choice1Of2 verifiedKidPoliteJob -> Choice2Of2 { Wishes = wishes; Id = verifiedKidPoliteJob.Id }
                | Choice2Of2 childWishJob-> Choice1Of2 { Wishes = { MachineLearningWishes = wishes; ChildWish = childWishJob.ChildWish }; Id = childWishJob.Id }

Enter fullscreen mode Exit fullscreen mode

"It's a good choice to use Choice" - said Harry and both elves laughed so much. The time has come and now our cheerful, laughing elves could recommend some gifts!

            let recommendGift (job: WishesCombined) (gift: Gift): RecommendedGift =
                { Wishes = job.Wishes; RecommendedGift = gift; Id = job.Id }
Enter fullscreen mode Exit fullscreen mode

A single red line of truth appeared - Harry rapidly added missing type:

        type RecommendedGift = private {
            Id: Guid
            RecommendedGift: Gift
            Wishes: Wishes
        }
Enter fullscreen mode Exit fullscreen mode

And this action silenced the unbending compiler.

They were so close. Harry started dreaming about independently deployable microservices orbiting around deep learning models, but then Barry reminded him what's the most important thing right now - "The domain" - Harry whispered. Elves needed to finish the process by completing the gift preparation job.

      let complete (job: Choice<WishesCombined, RecommendedGift>): Completed =
                match job with
                | Choice1Of2 wishesCombinedJob -> CompletedWithCombinedWishes wishesCombinedJob
                | Choice2Of2 recommendedGiftJob -> CompletedWithRecommendedGift recommendedGiftJob
Enter fullscreen mode Exit fullscreen mode

Barry and Harry looked at each other, silently nodded, and did, without any doubt or unclear thought, what was required to be done. It was pure as a snowflake falling from the sky.

type Completed = CompletedWithCombinedWishes of WishesCombined | CompletedWithRecommendedGift of RecommendedGift
Enter fullscreen mode Exit fullscreen mode

Epilogue

The time passed like a flash and suddenly A Type of Happiness Inc. company became a large software firm, hiring hundreds of elvish programmers. Barry and Harry tried to incorporate the spirit of focusing on clearly expressing the domain firstly, combined with F# usage because it fitted so well.

One day, both Barry and Harry discussed some difficult part of multi-cluster, multi-agent reinforcement learning microservice-based component when suddenly Barry asked:
- Harry, do you remember our first domain expressing session with fsharp?
- Of course Barry! Why do you ask?
- From that time our code evolved so much, but still, we're trying to use the compiler and our lovely language to document the domain, directly in the code. Although, we have some places in the codebase where we use object flavors of fsharp. Do you think the object-oriented way was wrong? - Barry continued.
-Well...No. It's perfectly fine. You know why Barry?- Harry paused for a minute to build the tension:

It's functional first, not functional only language

Discussion

pic
Editor guide
Collapse
limal profile image
Łukasz Wolnik

Have you become a writer now too? A man of many talents! Outstanding execution!

Could you provide some examples of an impossible scenario that could be applied to the first version of the code but not to the later one thanks to the typing?

Collapse
budibase profile image
Budibase

Wonderful read! I like your writing style too; it really brings the post to life.