Apache Camel – Enterprise Integration met scripttalen en DSLs

30 March 2009 16:21 Peter Maas Java

“Er zijn twee dingen moeilijk in de IT”, vertelde een collega me ooit: “Een printer installeren en communiceren met een extern systeem”. In de praktijk blijken beiden vaak waar. Met printers kan ik jammer genoeg niet helpen. Met het oplossen van integratievraagstukken hopelijk wel.

Om me heen zie ik (vooral aan de randen van systemen) vaak integratieoplossingen in de vorm van stukjes maatwerk: importers, exporters, http gebaseerde services, cronjobs, conversie modules etc. Meestal begonnen met de beste bedoelingen maar uitgegroeid tot monolitische hopen spaghetti. Dit komt onder andere doordat de kennis van het externe formaat bijvoorbeeld pas werd opgedaan tijdens het project, het externe proces toch net anders werkte dan verwacht of doordat de complexiteit gewoon verkeerd was ingeschat.

Dit is best jammer, want over de integratie van verschillende systemen is al een hoop geschreven. Het boek Enterprise Integration Patterns bevat bijvoorbeeld een set ontwerpstrategieën om veel voorkomende problemen te tackelen.

Een boek met patronen is niet hetzelfde als een oplossing voor het integratievraagstuk. Gelukkig zijn er verschillende frameworks die met deze kennis als uitgangspunt het bouwen van integratiesoftware faciliteren. Een erg interessant framework dat dit doet is Apache Camel.

Routes

Camel is een compact framework wat je integratievraagstukken laat oplossen op basis van de Enterprise Integration Patterns middels een Java gebaseerde Fluent API, Spring XML of een Scala DSL. Het is ook mogelijk om deze drie vormen gezamelijk te gebruiken. Met behulp van Camel schakel je als ontwikkelaar componenten aan elkaar op basis van URI’s. Hierdoor werk je met eenvoudige, makkelijk te lezen code. Alle details omtrent specieke transport- en berichtmodellen zoals HTTP, ActiveMQ, JMS, JBI, SCA, MINA of CXF Bus API vertroebelen daardoor de code die er toe doet niet. De lichtgewicht aard van Camel zorgt ervoor dat het eenvoudig is op te nemen in een bestaande architectuur.

Als je ontwikkelt met Camel denk je in routes. Een route heeft een beginpunt en een eindpunt. Het beginpunt is bijvoorbeeld het externe systeem waar je data van wilt ontvangen, het eindpunt je eigen applicatie of omgeving. Een eenvoudige route (met de Java DSL) ziet er bijvoorbeeld zo uit:

package com.finalist.eip.examples;
 
import org.apache.camel.builder.RouteBuilder;
 
public class FileCopyRoute extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    from("file:src/data/")
      .to("file:target/data");
  }
}

Met de Camel maven2 plugin kun je hiervan eenvoudig het volgende plaatje genereren (‘mvn camel:dot’):

camel_file_copy_example

Bovenstaande voorbeeld scant naar files in de directory ‘input’, schrijft gevonden files naar de directory ‘output’ en bewaart een kopie van de verplaatste files in ‘input/.camel/’. De ‘file:xxx’ URI vertelt Camel dat we het file component willen gebruiken. Het file component kan (zoals de meeste componenten in Camel) zowel als ‘producer’ (ingang) en als ‘endpoint’ (uitgang) worden gebruikt. Naast een file component komt Camel met een groot aantal andere componenten. Denk daarbij aan ftp, jms, hibernate, ibatis, rest, soap, http, tcp, seda etc.

Naast begin- en eindpunten kun je een route uitbreiden met componenten die bijvoorbeeld transformeren, routeren of verrijken. Met een paar toevoegingen maken we een eenvoudige applicatie die de gevonden files opsplitst en per gevonden element een bericht op een Topic queue (JMS) zet. Een andere route luistert naar berichten op de betreffende queue (De queue is puur ter illustratie, routes kunnen ook via één van de andere componenten met elkaar communiceren).

package com.finalist.eip.examples;
 
import static org.apache.camel.builder.xml.XPathBuilder.xpath;
import org.apache.camel.builder.RouteBuilder;
 
public class GatewayService extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    // retrieve files and split contents
    from("file:src/data/")
      .split(xpath("//subscription"))
      .setHeader("id", xpath("/subscription/@subscriptionId").stringResult())
      .setHeader("arpu", xpath("/subscription/@arpu").stringResult())
      .to("activemq:topic:incomingSubscription");
  }
}
package com.finalist.eip.examples;
 
import org.apache.camel.builder.RouteBuilder;
 
public class SubscriptionDistributionService extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    // listen to topic and distribute
    from("activemq:topic:incomingSubscription")
      .choice()
        .when(header("arpu").isEqualTo("high"))
          .to("file:target/additionalCheck/?expression=subscription_${in.header.id}.xml")
        .otherwise()
          .to("file:target/defautProcess/?expression=subscription_${in.header.id}.xml");
  }
}

De ‘expression’ parameter in het ‘file’ endpoint (’${in.header.id}’) zorgt ervoor dat de geschreven files een naam krijgen die is afgeleid van de binnengekomen data (het subscriptionId attribuut). Je hoeft overigens URI’s niet op deze wijze op te bouwen, als het complexer wordt zou je er ook headers voor kunnen gebruiken. Van bovenstaande code wordt het volgende diagram gegenereerd:

camel_routing_example

Bij het plaatsen van een file met deze content:

 
<subscriptions>
    <subscription arpu="high" subscriptionid="1203">high arpu 1</subscription>
    <subscription arpu="high" subscriptionid="1205">high arpu 2</subscription>
    <subscription arpu="low" subscriptionid="1208">low arpu 1</subscription>
    <subscription arpu="high" subscriptionid="1213">high arpu 3</subscription>
    <subscription arpu="medium" subscriptionid="1217">medium arpu 1</subscription>
</subscriptions>

Zullen er twee directories worden aangemaakt. De elementen met arpu attribuut ‘high’ worden in de ‘additionalCheck’ folder geschreven, de rest in de ‘defautProcess’ folder.

Processors

Daadwerkelijk iets doen met berichten in een route doe je met een processor. Een processor ziet er als volgt uit:

public class MyProcessor implements Processor {
  public void process(Exchange exchange) throws Exception {
    // do something...
  }
}

De exchange is het bericht wat over de route gaat, inclusief header, correlatie id etc. Camel komt standaard met opties om bijvoorbeeld spring beans als processor te configureren:

<beans>
    <!-- plain old spring configuration -->
    <bean id="myBeanId" class="com.acme.MyBeanProcessor"></bean>
</beans>

En dan in je route:

from("file:data/inbound")
  .beanRef("myBeanId", "processBody")
  .to("file:data/outbound");

Je kunt dus op ieder moment een bericht naar bijvoorbeeld een service van je applicatie routeren.

Parlez vous Français?

Omdat niet iedereen dezelfde XML spreekt komt Camel met een generieke implementatie van de het Message Translator pattern. Hiermee is het mogelijk om externe formaten om te zetten in bijvoorbeeld java code. Zo kun je bijvoorbeeld CSV omzetten in een List<List<String>>:

from("file:src/data/addresses.csv").
  unmarshal().csv()
    .to("...");

Ook kun je eenvoudig CSV schrijven. Verder is er ook support voor bijvoorbeeld EDI, HL7, JSON, ZIP en een reeks andere formaten. En je kunt natuurlijk ook altijd je eigen DataFormat schrijven.

Polyglot

Ofwel meertalig. Camel is JSR 223 compliant zoals dat zo mooi heet. Oftewel als jouw favoriete scriptingtaal dat ook is werkt het samen. JRuby, Groovy Jython, Jaskel, Javascript, PHP om maar wat te noemen. Zeker bij een integratieframework is dat erg relevant. Je kunt zou bijvoorbeeld een stuk Ruby DSL kunnen gebruiken om bepaalde beslissingen te nemen.

from("direct:start")
  .choice()
    .when().ruby("$request.headers['user'] == 'admin'")
      .to("seda:adminQueue")
    .otherwise()
      .to("seda:regularQueue");

Je hoeft code natuurlijk niet persé inline te schrijven, in veel gevallen kun je vanuit de scripting talen natuurlijk prima de verschillende interfaces implementeren.

Naast scripting talen is er ook uitgebreide ondersteuning voor het schrijven routes in Scala. Overigens moet ik zeggen dat ik (normaliter gek van scripttalen, en groot fan van Scala) door de mooie fluent interfaces in Java en de kracht van XPath nog geen gebruik heb hoeven maken van al dit moois. Maar in het kader van ‘het beste gereedschap bij het probleem zoeken’ denk ik dat je al sneller naar deze opties zult grijpen wanneer je JSON of een POJO model in plaats van XML als bericht formaat gebruikt .

Testen

Camel komt met een speciaal testframework met verschillende gereedschappen om tests voor Camel componenten en routes te schrijven. Eén van die componenten is het Mock component. Het Mock component kun je gebruiken om te kijken of een bericht ergens aan komt en hoe het er dan uit ziet. Het Mock component gebruik je net zoals de andere componenten waarna je zoals je dat van bijvoorbeeld JMock kent vraagt of het blij is:

// route met mock endpoint
from("file:src/data/")
  .to("mock:result");    
 
// vertel hoeveel berichten je verwacht
MockEndpoint resultEndpoint = context.resolveEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(2);
// doe stuur berichten naar je route
...
// kijk of aan alle kriteria van het Mock object is voldaan
resultEndpoint.assertIsSatisfied();

Deze gist bevat een uitgebreider voorbeeld van een test van de GatewayService uit een eerder voorbeeld.

Conclusie

Camel biedt je een elegante oplossing voor het uitwerken van integratievraagstukken. Je kunt denken en ontwikkelen in grove lijnen met behoud van vrijheid om naar detailniveau af te dalen waar nodig. Door de (op Maven 2 gebaseerde) structuur is het een net, modulair framework dat eenvoudig is op te nemen in een bestaande of nieuwe architectuur. De goede integratie met scripttalen, en het uitgebreide assortiment aan reeds ondersteunde formaten maken Camel een ideaal stuk gereedschap wat je wat mij betreft altijd in je koffer zou moeten hebben.

3 reacties »

  1. Leerzame post!

    P.S. Printers én _scanners_ zijn moeilijk te installeren. :o )

    Nils Breunese March 30, 2009 23:21

  2. Hi

    I dont read dutch but the article looks very well written and organized. Even with camel:dot diagrams.
    Keep it up.

    If you like I can add a link to this article at our articles page at:
    http://camel.apache.org/articles.html

    Regards

    Claus Ibsen
    Apache Camel rider

    Claus Ibsen April 4, 2009 23:51

  3. @Claus:

    Thank you for adding the reference, much appreciated!

    Peter Maas April 16, 2009 13:09

Reageer

RSS feed for comments on this post · TrackBack URI