Search code examples
apache-camelakkaactorthread-synchronizationenterprise-integration

Does Akka natively support Integration Patterns?


I am new to Akka and am trying to figure out whether it has built-in support for Enterprise Integration Patterns (EIP) or whether I need to delegate this type of routing/integration out to a framework like Camel.

In my use case, I have an actor that reads binary samples from a source (file); this actor is called the Sampler. The Sampler than passes Sample instances (messages) out to a field of actors called SampleProcessors. Each sample processor does something different to a given Sample. Depending on the outcome of a processor processing a Sample, it may need to be routed to 1+ other SampleProcessor, or perhaps all processing has concluded. Depending on the exact SampleProcessor, and the exact nature of the given Sample, the Sample may need to be multi-casted to a list of other recipient SampleProcessors.

This all feels like Camel to me.

So I ask:

  • Does Akka have built-in support for routing, broadcasting, multi-casting and other EIPs (if so, what are they and where are they documented)?
  • Or, should I try to integrate the actor system with Camel, in which case, what would that look like? I know there is a Camel-Akka component but I believe this is just for integrating a Camel bus with an actor system (whereas, I want a service bus inside my actor system)
  • Or, should I just do my own homegrown EIP/actor wiring here?

Solution

  • Akka doesn't suport EIP natively, but there is several ways to implement it.

    Anyway, if you want some handy DSL, there is a better idea than EIP - as it's done with GoF-patterns, you can replace (implement) most of EIP-patterns with functional composition + Functors (map) and Monads (flatMap). In other words, you can treat input stream as infinite collection. So,

    • processors become functions
    • pipes become functors, like val output1 = input.map(processor1).map(processor2)
    • routers and filters become... monads (filter is based on flatMap):

      val fork1 = output1.filter(routingCondition1).map(...)

      val fork2 = output1.filter(routingCondition2).map(...)

    • split is flatMap: input.flatMap(x => Stream(x.submsg1, x.submsg2))

    • aggregators become catamorphisms, aka fold (accumulator should be usually backed by some storage)

    Such stream-based workflow is already implemented for Akka and it's called Akka Streams, which is an implementation of Reactive Streams, see also this and that articles.

    Another option is to use Akka as is, actor guarantees sequential processing, so you can implement piping, by creating the chain of actors:

    class Processor1(next: ActorRef) extends Actor {
       def receive = {
          case x if filterCondition => 
          case x => next ! process(x)
       }
    }
    
    val processor2 = system.actorOf(Props[Processor2])
    val processor1 = system.actorOf(Props[Processor1], processor2)
    

    If you need routing - it's just two "next"'s

    class Router(next1: ActorRef, next2: ActorRef) extends Actor {
       def receive = {
          case x if filterCondition => 
          case x if cond1 => next1 ! process(x)
          case x if cond2 => next2 ! process(x)
       }
    }
    

    If you need to guarantee no races between routes - see this answer. Of cource, you loose the whole DSL idea, using actors directly.

    P.S. Yes, you can still use Camel for endpoints - Akka have some support for that. And you can use Akka as service-activator.