Functional Domain Programming Cookbook

In this article I will try to show how Typed Functional Programming can be used to represent a domain. I would run through a step by step example where different domains are modelled with types and subtypes and functions are used to transform one domain to the other.

Let’s consider the process to make a cake that is described in a recipe. The first thing is the list of ingredients.

sealed trait Ingredient
case object Flour extends Ingredient
case object BakingPowder extends Ingredient
case object Water extends Ingredient

This list is written in the recipe but we are not sure that we have all the ingredients in our kitchen. To express this situation we create another type.

sealed trait NeededIngredient {
  val ingredient: Ingredient
}
case class MissingIngredient(ingredient: Ingredient)
       extends NeededIngredient
case class PresentIngredient(ingredient: Ingredient)
       extends NeededIngredient

With these two types we can define the first operation needed to start the cake.

def getIngredients(ingredient: Ingredient*): List[NeededIngredient]

The result of this function is the list of needed ingredients where can be present or missing. We could have used the Option type from the Standard library but it wouldn’t have had the same expressivity. In this case is clear we would like to know which ingredient is missing while None would have hidden this information.

The next part of the recipe is mixing the list of needed ingredients.

def checkAndMix(ingredients: List[NeededIngredient[Ingredient]]):
      Either[MissingIngredients, Mixture]

The checkAndMix function result can be either a mixture or a list of missing ingredients.

case class Mixture(ingredients: List[Ingredient])
case class MissingIngredients(ingredients: List[Ingredient])

We could’ve have chosen another way to represent the result of the function. Instead of Either we could have created another type.

sealed trait Mixture
case class FullMixture(ingredients: List[Ingredient])
case class PartalMixture(present: List[Ingredient],
                         missing: List[Ingredient])

This solution can look correct and even more elegant than the first one but it is not valid for domain: if you don’t have all the ingredients for a cake you don’t start mixing, it would be a waste!

Either is actually the right solution because represent a disjunction of domains. A disjunction means that two completely differents domains have only one thing in common: they are both valid result of the function.

The last thing to do with the mixture will be baking.

def bake(mixture: Mixture): Cake

As you can see this function takes only the mixture as input and, from the domain point of view, is the right definition. In fact what we want to express is that, after we successfully mixed the ingredients, we can bake the mixture. The Either type from the standard library offers a solution that is perfect for this kind of situations: map on right.

val neededIngredients = getIngredients(Flour, Water, BakingPowder)
val possibleMixture = checkAndMix(neededIngredients)
val possibleCake = possibleMixture.right.map(bake)

The function is applied only if the result is correct (if it is right) and the left value remains as is.

Another possible implementation of the bake function could have possible baking issues.

def bake(mixture: Mixture): Either[BakingIssue, Cake]

Once again this solution can be applied only to the a mixture but the result has another domain disjunction.

val possibleCake1: Either[MissingIngredients, Cake]
// or
val possibleCake2: Either[MissingIngredients, Either[BakingIssue, Cake]]

If we compare the final result type of the two implementations we can see that the type is more complex and not so easy to read. Instead, it would be nicer something that clearly shows the final result, the cake, and the possible problems that we can have during the preparation.

val idealCake: Either[OneOfTwo[MissingIngredients, BakingIssue], Cake]

This ideal solution type would combine the positive and negative transformation paths where the issues are listed in a single type but only one can be present.

sealed trait OneOfTwo[+A, +B]
case class First[A](a: A) extends OneOfTwo[A, Nothing]
case class Second[B](b: B) extends OneOfTwo[Nothing, B]
// convert
def convert[A, B, C](either: Either[A, Either[B, C]]):
  Either[OneOfTwo[A, B], C] = either.fold(
    a => Left(First(a)), {
      case Left(b) => Left(Second(b))
      case Right(c) => Right(c)
    }
)

This one is a possible simple implementation that converts the nested Either in a flat one with all the issues or domain disjunctions on the right hand side.

Functional.Programming

I like to think about Functional Programming as another level of abstraction that brings the code far from the physical machine, maybe like the next step after the Memory Management that has hidden the memory location. I like to think about Functional Programming as a guidance to create an application without memory assignments.

It is all about Immutability

What is the problem with memory assignment; or in other words, what is the problem with mutable data? In a context where every software runs in parallel, shared mutable states are a problem, a big problem. Plenty of solutions has been built to solve it but at the end they all imply a sort of synchronization with locks and waitings and potentially performances and scalability issues.

Another way to solve the problem is by avoiding it in the first place, so writing a software without any shared mutable states; or in other terms, using exclusively immutable data structures.

In this context a paradigm that works well with immutability can very attractive. Functional Programming fits very well in this role and it can be seen like a discipline that helps to abstracts the software from the need of memory assignments.

Be Functional

How well Functional Programming fits with immutability? The base principle of Functional Programming is transformation: the function takes an input, applies a transformation and returns an output. This principle has the same expressivity of any imperative code based on memory allocations. Functional Programming offers tools, patterns and disciplines to guide the developer to code with this different paradigm that does not involve memory assignment.

Let’s compare an example of imperative code that calculates the biggest value in a list with the functional version.

List<Integer> list = Arrays.asList(3, 6, 5);
int max = Integer.MIN_VALUE;
for (int value : list) {
  if (value > max) max = value;
}

In this example the imperative code uses the max variable to store the final result while the functional code uses the max parameter.

int calculateMax(List<Integer> list, int max) {
  if (list.isEmpty()) return max;
  else {
    int head = list.get(0);
    List<Integer> tail = list.subList(1, list.size() - 1);
    int newMax = head > max ? head : max;
    return calculateMax(tail, newMax);
  }
}
List<Integer> list = Arrays.asList(3, 6, 5);
calculateMax(list, Integer.MIN_VALUE);

All the other concepts around Functional Programming like pipelines, higher order functions, compositions make more efficient or easier working with immutable data but at end the idea is very simple and it is all about data transformation.

Be Pure

An entire application can be seen a single function, a huge complex function of thousand and thousand lines of code. It is clear that such a thing is not a good practice and luckily functions can be easily modularized: more than one function can be composed together to create a bigger transformation. A simple definition of function composition is using the output of one function as input of another one.

int f(double input) { ... }
String g(int input) { ... }
String output = g(f(1.2));

Pure functions are functions with no side effects: meaning, when a function is called all the observable effects are in the output. If you think about composition you can see how important is the role of the output because it defines the interface between the two functions. For example If the first function throws an Exception the second function would not be able to catch it and it would be out of the transformation flow.

Be Typed

A typed language helps to define the signature of a function: more the type is precise, more the composition is safe. The type assumes the role of interface between functions and consequently part of the software validation is done by the type system through the compiler.

Operating On The Edge Of Failure… of MicroServices

Systems have Always been Complex

The complexity of a system is the correlation of the problem is aiming to solve and the technology is made of.

Micro Services Architectures bring solutions that can solve problems when we are in a context of scalability, high performance and need for quick changes. These solutions are characterized by strong decoupling and redundancy.

These architectures are also good to solve complex problems because they modularize in sub-problems. On the other side the macro-system has to handle the cohesion of small services is made of and this introduces new type of failures that were unknown to a standard monolithic application.

It seems that Micro Services have solved some problems but have caused new ones. The reality is that the problems are not related with the architecture but to the complexity. Choosing an architecture instead of another one will change only the type of problems rather then remove them.

Richard Cook, in his talk, describes how a complex system has always problems at the point we should consider the failures as part of the context, not exceptions.

The edge of failure

A system is operating between three boundaries. When the system crosses these boundaries stops working or stops to have a reason to work.

edge-of-failure

The first boundary is the Economic Failure. An application exists until it makes sense from an economical prospective. The company or the management is working to push the application away from this boundary, for example asking for new features or changes.

The second boundary is the Unacceptable Workload. An application requires an amount of work in order to run or to in order to be modified. This amount should be bearable and the developers or the people are working on the project are always pushing away from this boundary, trying to minimize the effort needed.

The last boundary is the Accident that defines the point after which the application stops working. Stop working has different meaning for different application and it changes to time to time.

It has been observed the applications tend to run close the Accident boundary with a certain distance defined by the Error Margin. This margin is the developers confidence in how close they can operate and make decision without causing an accident.

Micro Services Failures

The Micro Service architecture has bought inside the error margin new types of accepted failures, failures that in different architectures would be considered catastrophic or unacceptable. These errors are caused by the unreliable communication and the possibility of failing machines. This is considered normality in a Micro Service architecture so the each applications has to handle.

Reliability

This new class of failures has given to the reliability an important role into the architecture design. The reliability should be part of the application since the beginning, it should be build with every components that interact with unreliable resources or critical systems.

Actor.Messages.As.Methods

One of the best expedient to explain the interactions between Actors is thinking as method calls. It is useful to look at in this way during the design phase because it helps to better structure the code.

A method is identified by the name, the arguments and the return value. The first describes the operation, the second the data necessary to complete it and the last one the result.

The method is also defined in a class or and interface that defines its context.

class Contact {
  public boolean addNumber(String number) {
    ...
  }
}

For example a method without name that takes a string and returns a boolean can be very generic, while the method addNumber is more specific. The same method name can also have different meaning if defined in a different class.

A message in the Actor model is an immutable class that is sent to an actor. The concept is the same of method: the class name is the method name and the properties are the method arguments. The actor is context of the message like the class is for the method.

Method Name      = Message Class Name
Method Arguments = Message Class Properties
Method Class     = Actor

The message response is similar to the return value but contains also information about the origin of the message (operation and actor).

Four lessons

  1. The method name should be meaningful, the message name should be the same.
  2. There are generic methods (toString), there are generic messages (PoisonPill). All the specif messages to an actor should be used only by the actor (into the companion object).
  3. The method should do one thing, the actor should treat a message in the same way. Avoid to use parameters meaning different operations, like boolean switcher. Create instead another message as you would create another method.
  4. The result of a message tells also the origin of the messages. Avoid generic or shared messages, use actor specif result messages.

Actor.Types

The actor in the actor model is defined in Wikipedia as “the universal primitives of concurrent computation”. This definition places actors at the same level of language primitives like if, for, while, etc… in a concurrency context.

Like a primitive, an actor can be used to solve the problems in many way and, like the name suggests, can act a different role that is specific to solve the problem.

Here a not exhaustive list of type of possible types of actor.

Actor as Worker

The actor acts as worker of a specific operation, it has not state and it is specialized to solve the problem.

class Worker extends Actor {
  def receive = {
    case Operation(data) => ...
  }
}

The same operation can be parallelized creating many instances of the same actor with a router (see the documentation).

val router: ActorRef =
  context.actorOf(RoundRobinPool(5).props(Props[Worker]),"router")

The worker runs in a different context from the invoker (the component that has sent the operation) and the other workers. This isolation is used for obtaining parallelism but it also has benefit to separate blocking or failing operations.

Mapping Resources

When a worker (or a groups of workers) maps an external resource, like a connection pool, it creates a protection from the rest of the application. This protection can isolate an external blocking resource if the worker runs in a different thread pool or dispatcher (link to the documentation).

worker-dispatcher {
  type = Dispatcher
  executor = "fork-join-executor"
  throughput = 100
}

In this way only the worker thread is blocked and not the rest of the application.

The dispatcher (thread pool) can be easily associated to a router.

val router: ActorRef =
  context.actorOf(
     RoundRobinPool(5).props(Props[Worker])
     .withDispatcher("my-dispatcher"), "router")

In case of a pool of resources it is possible to match the size of the pool with the number of workers (connection pool of 10 = a router of 10 actors).

Failure of a Worker

The isolation of the worker can be useful to handle failures without the risk of propagation to the rest of the application. The worker can for example implement a retry logic or a circuit breaker or can benefit of the supervisor model, escalating the failure to the parent actor (see the documentation).

Domain Actor

The domain actor represents an instance of a single domain object like for example a person identified by first and second name. The life cycle of the object and the actor is the same, as is the status. The actor messages are the way the object interacts with the rest of the world.

class Person(firstName: String, secondName: String) extends Actor {
  var status = Status()
  def receive = {
    case Create(status) => ...
    case Read() => ...
    case Update(change) => ...
    case Delete() => ...
  }
}

An example of domain actor is the one that maps a single entity, like a row in a database table. Due to the nature of the actor all the operations on the entity are atomic, so this model can be adopted to implement transaction when the storage system doesn’t support it.

The domain actor status is important because it is the entity status and must be protected to not lose it in case of failure. So linking the actor to a storage system is a natural consequence and can be done synchronizing when a change occurs. In this way the actor can crash, can be stopped and restarted without loosing any data.

An actor can persist its status storing all the events that change it. This is know as the event sourcing model and it has been implemented into the Akka Persistence module.

class Person(firstName: String, secondName: String) extends PersistentActor {
  def persistenceId = firstName + "-" + secondName
  val receiveCommand: Receive = {
    case create: Create => persist(create) { event => ... }
    case Read() => ...
    case update: Update => persist(update) { event => ... }
    case delete: Delete => persist(delete) { event => ... }
  }
  val receiveRecover: Receive = {
    case Create(status) => ...
    case Update(change) => ...
    case Delete() => ...
  }
}

The actor persists the event before to update the internal status. This operation is not needed in case of read operations. The persistence actor is identified by the persitenceId that is used as key during the storing and recovery process.

Event Sourcing vs Status Persistence

The vantage of an event sourcing model is the possibility to rebuild the internal status of the actor from the events have contributed to build it. During this process the actor may have changed its behavior that can differ to the initial one. Replacing only the status would not help in this case.

Request Actor

The request actor is an actor created to satisfy a single request. Its status represents the initial request and the progress. A new actor is created for each request and when the request is completed the actor is stopped.

class RequestActor(<request parameters>) extends Actor {
  val requestProgress = ...
  override def preStart() {
    <start interacting with the rest of the system>
  }
  def receive = {
    case MessageFromSystem() => ...
    case LastMessageNeeded() =>
      <send request response back>
      context stop (self)
  }
}

Binding a request to an actor finds vantages in the status. The actor can track the progress, reacts to failures, implements retry logic and can make decisions that influence the result of the request.

Builder Pattern

In Java one of the most powerful ways to create an instance, except for dependency injection, is using the builder pattern.

The builder pattern helps to create immutable object and avoid to use long or many constructors.

The immutability in Java beans can be guaranteed defining all the attributes final, so once set, they remain the same and the status of the object never changes. As consequence, the constructor should set all the attributes and if there are a lot it can cause misleading set. This because Java constructors use only the position to identify the attributes and place the parameters in the wrong order is very easy, especially of the same type.

public class Person {
  private final String name;
  private final String surname;
  private final Integer age;

  public Person(String name, String surname, Integer age) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }

  public String getName() { return name; }

  public String getSurname() { return surname; }

  public Integer getAge() { return age; }

}

In this example is easy to declare the Person class with name and surname inverted.

To avoid this problem is possible to use a second class, the Builder, where the constructor is replaced with methods with meaningful names.

public class Builder {

  private String name;
  private String surname;
  private Integer age;

  public Builder withName(String name) {
    this.name = name;
    return this;
  }

  public Builder withSurname(String surname) {
    this.surname = surname;
    return this;
  }

  public Builder withAge(Integer age) {
    this.age = age;
    return this;
  }

  public Person build() {
    return new Person(name, surname, age);
  }

}

The Builder class contains the attributes values until the object is built and each parameter is set through a method.

Person person = new Builder()
  .withName("Alessandro")
  .withSurname("Simi")
  .withAge(32)
  .build();

The disadvantage of this solution (although is a good solution) is the Builder in itself, because requires to have another object and it doesn’t guarantee is used instead of the Person constructor.

To improve the solution the Builder can be defined as inner class of the Person table and reduce the visibility of the constructor, so the object can be created only through the Builder.

public class Person {
  ...
  private Person(String name, String surname, Integer age) {
  ...
  public static class Builder {
    ...
  }
}

Still this solution has two drawbacks. First, the object can be built before all the parameters are set. Second, the Builder is not immutable.

Solving the first problem means introducing a chain of Builders where only the leaf contains the build method. This approach can, for example, divides the mandatory attributes from the optional ones and in general can be expressed through a Fluent Interface.

The immutability can be possible if every method return a new instance of the Builder, but can produce a lot of boilerplate code.

public class Person {
  private final String name;
  private final String surname;
  private final Integer age;

  private Person(String name, String surname, Integer age) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }

  public String getName() { return name; }

  public Integer getAge() { return age; }

  public String getSurname() { return surname; }

  public static AfterName name(String name) {
    return new Person(name, null, null).new AfterName();
  }

  public class AfterName {
    public AfterAge age(Integer age) {
      return new Person(name, null, age).new AfterAge();
    }
  }

  public class AfterAge {
    public Builder surname(String surname) {
      return new Person(name, surname, age).new Builder();
    }
  }

  public class Builder {
    public Person build() {
      return Person.this;
    }
  }
}

This solution defines the Builder as inner class so it can access to the attributes and create a new instance for each method call. It also forces the creation when all the properties are set.