Blog

Defining a custom Archipelago component

Archipelago library comes with many useful ready made and configurable components which can be used directly in your system specifications. In the case that the components available are sufficient for the task, then no code other than the specification will be necessary. For those bits within your domain that fall out of what it is provided, Archipelago provides an API to write custom components easily.

The first concept that one needs to think about when writing a new component is the component contract. The component contract specifies the type of messages that your component can receive and the type of messages your component will send.

To make this task easy Archipelago supplies a macro called @CellContract which takes a list of parametrized Receive[T] and Sends[T] objects which identify the types being received and sent respectively. Those objects can be provided in any order.

Once we have decided the component contract we can then declare the component clas. In Archipelago, we call those components "Cells" and each cell is just a class extending Cell[CFG] where CFG represents a configuration type for your cell.

The configuration type must extend CellCfg but other than that it can contain anything useful for the cell. This allows you to provide a custom configuration for your cell which will be supplied when declaring systems.

Finally, the body of the cell will just be a call to the message handling method called "message" which takes a partial function. This function will usually be a pattern matching on the types received by the cell with the actions the cell will take on response to those events. When it is time to send out a message, you just need to call .send on the message being send, for instance if your cell sends String you can directly do

"Some String".send

That's it:  Contract, Configuration and Message handlers. You might have noticed that the cell sends messages out but it does not say where. It also specifies what to do with received messages but it does not say where they come from. This is a fundamental feature allowing component composition. Given a cell A sending String and a cell B able to receive String they will be able to be linked in the system spec and B will receive those strings sent by A.

If you are familiar with Microservices, you might have noticed that a Cell is a ultra fine grained loosely coupled service with a well defined contract. So in other words it is a Nanoservice and when they are composed to build Reactors those in turn can be considered microservices themselves. Archipelago adds further control structures related to services, as we will explain in further blog entries.


A component definition example

Let's put all those concepts together and build a component that will receive a collection of Strings and send them out sorted according to a sorting function.

The contract will therefore contain Receives[Seq[String]] and Sends[Seq[String]] and the sorting function will be supplied in the cell configuration as a string comparison function which returns a boolean.

The cell will have one single message handler to act when a Seq[String] is received. The full definition is provided below.

trait StringSorterConfig extends CellCfg {
  def lessThan: (String, String) => Boolean
}

@CellContract(Receives[Seq[String]], Sends[Seq[String]])
class StringSorter extends Cell[StringSorterConfig] {

  message {
    case seq: Seq[String] =>
      seq.sortWith(cfg.lessThan).send
  }

}

The code excerpt above would work but it would also hint a warning at compile time because the non-variable type argument String in type pattern Seq[String] is unchecked since it is eliminated by erasure. To remove this we can use a Scala construct with  ClassTag. The final version of the StringSorter Cell is provided below.

@CellContract(Receives[Seq[String]], Sends[Seq[String]])
class StringSorter extends Cell[StringSorterConfig] {

  private val sct = classTag[Seq[String]]

  message {
    case sct(seq) =>
      seq.sortWith(cfg.lessThan).send
  }

}