Wednesday, 25 February 2009

Tool Support for DDD

I like Eric Evan’s book Domain Driven Design very much.  It’s such a nice distillation of a relatively small number of principles, all of which possess real, fundamental value. This is a protein-rich book: it may not feel as filling as the more prescriptive, methodology-driven ‘just-do-as-I-say-and-it-will-come-out-right’ books, but they’re just carbohydrate.  DDD is good for you in the long-term.

I’ve just discovered that you can read some of the book online, via Google Books.  You can also find out more from the DDD Site.

The real point of this post is to share my experience with a small, free tool called wikidPad, which I stumbled on last week when looking for a desktop wiki-based tool to use for capturing Ubiquitous Language (UL) definitions.  Capturing and refining the UL is an essential part of applying DDD, and having a fast, convenient (and open) medium for doing this is essential.  A wiki of some kind is almost ideal.

Most desktop or single-user wikis are either cut-down versions of the equivalent web apps (PHP or whatever), running on localhost ports, or they are single self-editing HTML pages e.g. TiddlyWiki.  Many of these (including TiddlyWiki) are very good indeed, but somehow, none of them has felt quite right for the UL use-case, which is all about getting hyperlinked definitions into a reusable form as quickly as possible.  wikidPad does this superbly well, in my opinion. 

It’s also a small and fast desktop application, which I find a little more convenient than using a wiki app in a browser. Some examples to support that:

  1. wikidPad automatically constructs a tree-view of pages on the left hand side of the application – this makes navigation very fast.
  2. wikidPad does auto-completion of wiki-words (Ctrl-space), which makes writing new entries which link to existing items very fast.
  3. Drag and drop support, and support for multiple tabs – again, all good for speed and usability.

When you’re ready to publish the UL to a wider audience, wikidPad will export the whole thing (or any selected part) as a set of HTML pages.

It’s my first day using this tool, so it may be that I’ll discover something to dent my enthusiasm. But so far, it’s all good.

Monday, 23 February 2009

Assembla

Technorati Tags: ,,

Although Project Locker has served me very well for a good many years, better alternatives have emerged recently. I didn’t set out to switch providers, only to see whether Project Locker would support Mercurial or git. At the time, neither were available – since then PL have added git support. It was while looking around at the many newer project hosting services that I came across Assembla.

The Assembla proposition is simple and very reasonable: public, open-source projects are hosted for free (up to 200MB). Private plan pricing is extremely keen:

  • $2.00 per user per space, per month
  • $3.00 per gigabyte of disk space per month
  • $8 per user per month maximum. After you pay for a team member in four spaces, additional spaces are free.

Private plans get unlimited disc space.

But for me, the economics of Assembla are only half the story: the other half is a combination of things: the clean, functional web interface, the range of additional tools (Trac, wiki, files, chat, project dashboard etc.) and a strong feeling that the people behind Assembla know what they are doing.

Assembla does offer git hosting, but I’m sticking with Subversion for now.  SVN is good enough for my purposes and I can’t live without TortoiseSVN.

I’ve been using Assembla for a couple of months and my experience has been very good indeed. 

Tuesday, 17 February 2009

Camel, HL7, HAPI and NAK Messages

The previous couple of posts were related to using Apache Camel in a healthcare integration setting.  The HL7 support in Camel is built on the HAPI library which provides Java classes for most of the HL7 v2 variants, plus MLLP support.  So in integration terms, HL7 can be considered a combination of an endpoint type (the MLLP/TCP protocol) and a message format (the HL7 ER7 or XML representation).

The Camel architecture nicely isolates endpoint-types and message-structure types in Components and DataFormats, respectively.  A Component is something which knows about a specific communication mechanism or protocol: in the case of HL7, the Camel HL7 Component knows how to exploit the underlying Apache Mina library to exchange delimited text messages using the HL7 protocol.  The Component also acts as a factory for Camel Endpoint objects: it hands out individual Endpoint objects, each of which represents an instance of the endpoint-type implemented by the Component. Camel DataFormat objects marshal / unmarshal byte-streams or strings to / from Java objects of some type.  This makes it much easier to build content-based routes in Camel.

HL7 ACK / NAK Messages

In the previous post, one of the issues I had to deal with in the example route was returning an ACK or a NAK to the message originator. The HL7 protocol uses a positive-acknowledgement scheme: receivers will ACKnowledge (ACK) messages they understand and can process, and will Negatively AcKnowledge (NAK) anything else.  The HAPI library provides support for this in the makeACK method of the DefaultApplication class.  Unfortunately, this is less helpful when we need to return a NAK.

Recall that the first part of my route was checking to see whether the right kind of message has been received. In the sample, I was testing just the message type (MSH-9.1) and the trigger event (MSH-9.2) fields.  In reality, you would probably also want to check that the sender was using the same version of HL7 as you are expecting: you can do this by examining the value of field MSH-12. It will become clear why this is important, below.

Suppose the inbound message fails the MSH-9 test. The otherwise() clause in my little route will send the HL7 data (unmarshalled from the stream) to the badMessage() method on my handler bean.  This method expects a single argument of type ca.uhn.hl7v2.model.Message, i.e. a generic HAPI HL7 message object.  The argument can't be of a more specific type than this because the Camel route builder cannot know in advance what will be received and unmarshalled from the endpoint; it can only assume it will be a HAPI Message.

Constructing an ACK

In the badMessage method, I need to construct a HL7 NAK message to be returned to the caller. HL7 ACKs and NAKs share the same structure: we just need to change the value of one field (and optionally add a diagnostic message) to indicate NAK. The obvious solution is to call DefaultApplication.makeACK, mentioned earlier, and modify the returned structure as necessary.  MakeACK's single argument is a single Segment, the intention being that the caller passes-in the MSH segment of the original inbound message:
Message makeACK(Segment inboundHeader)
The reason for this is that the makeACK method needs some values from the inbound MSH segment to populate fields in the Message Acknowledgement segment (MSA) of the ACK correctly. An HL7 ACK (or NAK) must be associated with the original message (i.e. the inbound message being ACK'd or NAK'd) so that the originator can distinguish which of its outbound messages is being acknowledged or rejected. This is done using the Message Control ID, which is field 10 of the MSH segment.

But remember where we are in the Camel route: we have an invalid message in our hands. We don't know the exact class-type of the inbound message object so we can't cast the Message to a specific type. And the Message interface itself doesn't provide a way to extract the MSH segment. So how are we going to call makeACK(Segment)?

We cannot simply new-up an instance of some specific message type (e.g. a version 2.3 ORM^O01) and use its MSH segment as the makeACK argument:

    // Assume ca.uhn.hl7v2.model.v23.*
   
    ORM_O01 orm = new ORM_O01();

    ACK ackMsg = (ACK)DefaultApplication.makeACK(orm.getMSH());        // <-- Bang!

    // Set MSA.1 to Application Reject (AR)
    ackMsg.getMSA().getAcknowledgementCode().setValue("AR");

    // etc.

This will compile, but won't work. The manufactured ORM_O01, despite being an instance of a specific version of HL7, is not properly constructed, in several ways. First, MSH-12.1 will not contain "2.3", which is surprising, given that HAPI should be able to populate MSH-12.1 for a new message instance. If we pass the ORM_O01 MSH to makeACK, it tries to get the value of MSH-12.1 but finds a null. This causes makeACK to fall-back to creating and returning an ACK for version 2.4 of HL7 (this is baked-in to the HAPI code - I'm not sure if this is an arbitrary HAPI design decision, or to do with the HL7 spec). And of course we get a ClassCastException at run-time as a result, because we are expecting a v2.3 object.

But even if this did work, isn't it ugly? We'd be baking-in a version of HL7 because we must choose a specific ORM_O01. So how do we create the ACK structure, and how can we make our code work for any version of HL7 supported by HAPI?

Terser to the rescue

Fortunately, HAPI provides a handy utility class for parsing and unparsing HL7 messages of arbitrary versions - the Terser. This will let us get hold of the MSH segment from the original message, so we could at least call makeACK with the original MSH:

    // This still depends on the ackMsg decl. being for the same specific version
    // as the original message.
    Terser t = new Terser(originalMsg);
    Segment msh = t.getSegment("MSH");
    ackMsg = (ACK)DefaultApplication.makeACK(msh);
    ackMsg.getMSA().getAcknowledgementCode().setValue("AR");

Now when we call makeACK we are passing-in a valid MSH so it will extract the HL7 version number and dynamically create the correct ACK class, using Class.forName.  But as the comment says, this stilll only works if the compiled-in ACK declaration matches the returned ACK message version. We need to lose the compiled-in ACK declaration, but still find a way to populate the ACK message's MSA segment with the right values.

Fortunately, we can use the Terser to set the field values in a version-independent way. This is exactly what DefaultApplication's static methods makeACK and fillResponseHeader do.  So I used a combination of the Terser and the guts of DefaultApplication.makeACK to create a couple of helper methods which do the right thing.  I've put the source code on this page in my Google site, for anyone who wants to use it.

Monday, 16 February 2009

Compound Predicates in Camel

Thanks to Claus Ibsen for such a helpful response to my previous post.  As Claus points out, the answer is to use a Camel helper class, PredicateBuilder, which contains a bunch of useful static combinator methods.  Claus shows in his post how to implement my trivial conjunction easily using the and() method.  Note the generic signatures of all these helper methods, e.g.

<E extends Exchange> Predicate<E> and(final Predicate<E> left, final Predicate<E> right)

Because they accept Exchange (or any derived class), any custom Predicate you might need to write has access to the complete message exchange structure (in, out and fault), and all predicates could be applied to a custom extension of Exchange.

Having got over those two 'humps' (sorry - couldn't resist that), I'm feeling very good about Camel.  It's a mighty impressive piece of work and looks like it has a lot of potential for the area I'm working in right now.

Thursday, 12 February 2009

Camel Routes and HL7

I've been spending some time with Apache Camel and ActiveMQ, with a view to using Camel in a healthcare integration project.  Camel has support for HL7 both at the message structure level (wrapping access to the HAPI libraries) and at the protocol level (via the Apache Mina extensions for HL7 MLLP).

As anyone who's looked at Camel (or IONA's FUSE Mediation Router) will know, Camel implements a rather nice Java DSL for building routes.  This lets you write things like:

from("hl7listener")
            .unmarshal(hl7Format)
            .choice()
              .when( header("hl7.msh.messageType").isEqualTo("ORM") )
                    .beanRef("hl7handler", "handleORM")
              .otherwise()
                    .beanRef("hl7handler", "badMessage")
              // end choice block- marshal the ACK/NAK back to the TCP endpoint
            .end()
            .marshal(hl7Format);

Very readable, but as you might expect, it hides quite a bit of complexity.  This is no bad thing, but you do need to understand what's going on behind the DSL. 

Camel exploits Spring, and in the above fragment "hl7listener" and "hl7handler" are bean names. The unmarshal and marshal methods allow DataFormat objects to convert between byte-streams and more convenient objects, such as HAPI messages.  The choice/when/otherwise operators allow predicates to control routing.

It's taking a while for me to grok Camel; here is a couple of things I'm not yet clear about:
  • Knowing how to get an intermediate stage in a route to return an ACK or NAK to the originator, without subsequent (or parallel) stages in the route from doing so. 
  • Creating compound boolean expressions in predicates. For instance, if I want a conjunction inside a when(), I can't have it. There don't appear to be combinators for expressions.  
In the fragment above for example, how can I express a conjunction/disjunction in the when( ... ) expression?  It only seems to be able to handle a single condition: I haven't found a way to combine predicates e.g. I can't do this:

when ( header("hl7.msh.messageType").isEqualTo("ORM") &&
             header("hl7.msh.triggerEvent").isEqualTo("O01") ).    // etc.

I have tried alternatives, e.g. nesting the when clauses:

from("hl7listener")
            .unmarshal(hl7Format)
            .choice()
              .when( header("hl7.msh.messageType").isEqualTo("ORM") )
                  .choice()
                    .when( header("hl7.msh.triggerEvent").isEqualTo("O01") )
                        .beanRef("hl7handler", "handleORM")
                    .otherwise()
                        .beanRef("hl7handler", "badMessage")
              .otherwise()
                  .beanRef("hl7handler", "badMessage")
            // end choice block- marshal the ACK/NAK back to the TCP endpoint
            .end()
            .marshal(hl7Format);

But this doesn't work either.  I'm just starting out with Camel, so I expect I simply haven't read the right bit of the documentation (which even the creators admit is one of Camel's weak points).

Another problem is controlling which component in the route returns a response to the originating endpoint (the MLLP sender, in the HL7 case).  I may have solved this one, but it's worth setting out the problem and my approach, in case it helps someone else.

My requirements are for the pipeline to log the inbound message (i.e. whatever is actually received on the HL7 socket), unmarshal the message to HL7 (HAPI), then validate the HL7 message type: at this stage in the pipeline I want to return a NAK to the sender (on the HL7 MLLP connection) if the message is not of the expected type, otherwise I want to write the message to a JMS queue for further processing and return an ACK.

The following route appears to work (note that I have omitted the message-logger stage):

        // We need the specific HL7 DataFormat object for unmarshalling HL7 ER7 from the
        // inbound MLLP link.
        DataFormat hl7Format = new HL7DataFormat();

        // Inbound HL7 comes from MLLP endpoint. 
        // Timestamp message, log receipt, unmarshal and perform initial
        // handling, which just determines if its the right kind of HL7
        // message.
        // If so, return ACK to originator and push message to JMS queue.
        // If not, return NAK to originator and end.
        //
        from("hl7listener")
            .unmarshal(hl7Format)
            .choice()
              .when( header("hl7.msh.messageType").isEqualTo("ORM") )
                    .marshal().hl7().to("jms:queue:orderMessage.queue")
              .otherwise()
                    .beanRef("hl7handler", "badMessage")
            .end()
            .marshal(hl7Format);
        
        // The order message processor - pulls message from JMS queue and sends to
        // the processor bean, which creates and returns an ACK to the hl7 channel
        // if the message is of the expected type.
        //
        from("jms:queue:orderMessage.queue")
            .unmarshal().hl7().beanRef("hl7handler", "handleORM"); 

All of this routing can be expressed in XML instead of using the Java DSL. Once I'm comfortable with Camel I may try writing a NetBeans plugin or RCP application to create visuals tools for Camel routes.