axon_logo.pngThe last weeks or maybe even months, I have spent time getting to understand the Axon Framework created by Allard. Axon is a framework that can help developers created a scalable and maintainable application based on Command Query Responsibility Segregation (CQRS) principles. Each morning Allard and I discussed the framework and the sample we wanted to have. Since I know my way around flex and Axon makes heavily use of events, I decided to create a flex client that could demonstrate some cool features of the Axon framework.

parsley-spicelib-vert.jpgThis post is mainly about flex and Parsley. We will go into depth of the architecture of the client and how to talk to the server. I will describe the communication with the application that makes use of the axon framework, but I will not go into a lot of details. If you want more information about the Axon Framework I suggest you go to the website http://www.axonframework.org. There is good documentation available in the reference manual. If you want to learn about flex and the Parsley framework in general, please read on.


What is Parsley and why use it?

Parsley is all about decoupling. It is a dependency injection framework and is has strong support for messaging. I like the way the dependency injection as well as message handling is configured. By using [INJECT] on a parameter, you tell parsley to inject an object of the type as specified by the parameter. We will see examples of the usage later on. Other things to inject are the [MessageDispatcher] that enables dispatching messages from each component.

The final component I want to mention is the DynamicCommand object. I use it more as a controller, still it is an interesting concept. We create one component that receives a message, does a remote call and handles the result of that call. All in one object. Again, an example will follow later on.

Structure of the Parsley solution

The following image gives you an idea of the overall solution of parsley. The image shows you the relationship between the view components, the controllers and the model. Messages are send from the view to the controller. The controller interacts with the remote components, updates the model and determines which view should be active. The controllers can also respond to remote push notifications, more on that later on.

parsley-overview.png

Configure parsley

To enable injection and message handling, parsley needs it’s own context to be setup. The easiest way to do this is using an mxml object and an mxml ContextBuilder object. The next code block shows you the complete configuration of the framework. Within the configuration you can see the controllers being configured as DynamicCommand objects. You also find there the RemoteObject, the Consumer and the model objects. They should all be familiar from the previous image.

<mx:Object xmlns:mx="http://www.adobe.com/2006/mxml"
           xmlns:sf="http://www.spicefactory.org/parsley"
           xmlns:commands="org.axonframework.examples.addressbook.commands.*"
           xmlns:model="org.axonframework.examples.addressbook.model.*"
           xmlns:cons="org.axonframework.examples.addressbook.consumer.*"
        >
    <mx:RemoteObject
            id="remoteAddressService"
            destination="addressService"
            endpoint="messagebroker/amf"
            showBusyCursor="true"/>
    <cons:Consumer/>

    <sf:DynamicCommand type="{NewAddressController}" messageType="{NewAddressMessage}"/>
    <sf:DynamicCommand type="{NewContactController}" messageType="{NewContactMessage}"/>
    <sf:DynamicCommand type="{SearchAddressController}" messageType="{SearchForAddressesMessage}"/>
    <sf:DynamicCommand type="{SelectContactController}" messageType="{SelectContactMessage}"/>
    <sf:DynamicCommand type="{ShowContactsController}" messageType="{ShowContactsMessage}"/>
    <sf:DynamicCommand type="{UpdatedContactController}" messageType="{UpdatedContactMessage}"/>
    <sf:DynamicCommand type="{UpdatedContactAddressController}" messageType="{UpdatedContactAddressMessage}"/>
    <sf:DynamicCommand type="{RemoveAddressController}" messageType="{RemoveAddressMessage}"/>
    <sf:DynamicCommand type="{RemovedItemController}" messageType="{RemovedItemMessage}"/>
    <sf:DynamicCommand type="{RemoveContactController}" messageType="{RemoveContactMessage}"/>

    <model:AddressModel/>
    <model:ContactModel/>

    <mx:Script><![CDATA[
        import org.axonframework.examples.addressbook.commands.*;
        import org.axonframework.examples.addressbook.messages.*;
        ]]></mx:Script>
</mx:Object>

That is all that parsley needs to know. The final step is to build the context using the special mxml tag. I have included the next tag in my Main.mxml.

	<sf:ContextBuilder config="ParsleyConfiguration"/>

Next we will focus on the different components

Messages

We make use of two types of messages:

  • CommandMessage – tells the receiver to do something.
  • NotificationMessage – tells the receiver that something happened

View

Each view component should focus on presenting data and sending messages. The data is injected as a model object. Messages can be dispatched by the special parsley provided dispatcher. Than parsley also provides a Context object that can be used when pop-ups are needed. The following code block shows you the configuration of these mentioned parameters from the ContactsView.mxml component. You can also find a function that is called by pushing a button to send a message to obtain details about a certain contact.

[Inject]
[Bindable]
public var contactModel:ContactModel;
[MessageDispatcher]
public var dispatcher:Function;
[Inject]
public var context:Context;

private function showContactDetails(event:ListEvent):void {
    dispatcher(new SelectContactCommandMessage(event.currentTarget.selectedItem as Contact));
    currentState = 'detail';
}

This is all pretty straightforward, if you need the code check out the sources from the google code project. Let us follow what happens when obtain details button is pushed and the SelectContactMessage is dispatched.

Controller

By dispatching this message, parsley selects the SelectContactController since the argument of the execute method is that exact message. The following code block gives you the complete implementation of the SelectContactController. The super class of all controllers contains a method to handle the faults coming from the remote calls and it makes the dispatcher as well as the remote object available. This class is also in the code block. In the code check how the dispatcher, the RemoteObject and the contactModel are injected. Another thing to notice here is that we only go to the server if we have not cached the contact details locally. So what if the contact details get updated on the server? Or if new contacts are added on the server? More on that in the next section.

public class BaseController {
    [MessageDispatcher]
    public var dispatcher:Function;

    [Inject(id="remoteAddressService")]
    public var addressService:RemoteObject;

    public function BaseController() {
        // default constructor
    }

    public function error(fault:Fault):void {
        dispatcher(new ErrorNotificationMessage(fault.faultString));
    }

}

public class SelectContactController extends BaseController {
    [Inject]
    public var contactModel:ContactModel;

    private var findAddressesFor:Contact;

    public function SelectContactController() {
        super();
    }

    public function execute(message:SelectContactCommandMessage):AsyncToken {
        var cachedContact:Contact = contactModel.findContactByIdentifier(message.contact.uuid);
        contactModel.selectedContact = cachedContact;
        findAddressesFor = cachedContact;

        if (!cachedContact.detailsLoaded) {
            return addressService.obtainContactAddresses(message.contact.uuid);
        }
        return null;
    }

    public function result(addresses:ArrayCollection):void {
        var cachedContact:Contact = contactModel.findContactByIdentifier(findAddressesFor.uuid);
        cachedContact.addresses = addresses;
        cachedContact.detailsLoaded = true;
    }
}

You have now seen the most important parts of the mechanism to go from a view to a controller. Did you notice that we send an ErrorNotificationMessage when something goes wrong in the server communication? The notification messages are handled in the OverallView component. Other notification messages are also handled in this component. The following code block gives a piece of code that handles the message.

[MessageHandler]
public function handleActivityLogEvent(message:NotificationMessage):void {
    createNotification(message.message, Notification.INFO);
}

Communication with the server

The server is used to send data to and of course to obtain data from. The easiest way is when the flex client takes control. As we saw in the parsley configuration, the remote object is configured as you would in any flex client. In the code from the SelectContactController we can see the call to the server. The result function is called when the server returns a result. This is not very complicated to understand.

Let us have a look at how this is handled on the server. Using spring blazeds integration this becomes very easy as well

BlazeDS and Spring BlazeDS Integration

Doing flex remoting with the Spring BlazeDS integration is so easy that anybody can use it. In the web.xml you configure the dispatcher servlet. The config file is loaded using the following tag in a spring configuration file.

<flex:message-broker services-config-path="classpath*:services-config.xml"/>

Now I use annotations to explicitly export some of the functions as remote services. The following code block gives you the example of obtaining address details for a contact. Notice that the name of the method is the same as the method call used in the flex code. By adding the annotation @RemotingInclude we tell spring to expose this as a remote service using BlazeDS.

    @RemotingInclude
    public List<AddressDTO> obtainContactAddresses(String contactIdentifier) {
        List<AddressDTO> foundAddresses = new ArrayList<AddressDTO>();

        List<AddressEntry> addressesForContact =
                repository.findAllAddressesForContact(UUID.fromString(contactIdentifier));
        for (AddressEntry entry : addressesForContact) {
            foundAddresses.add(AddressDTO.createFrom(entry));
        }
        return foundAddresses;
    }

The repository is based on the query database of Axon. I am not going into all the Axon Framework details here. Check the references if you want to learn more about Axon.

That is all there is to it, now you can obtain data from the server. Sending data to the server is done exactly the same.

Receiving events

I already mentioned that we cache data in the flex client. Still we want to have up-to-date data. Using the Axon framework and Spring BlazeDS integration it is not hard to push data to the client from the server when new or updated data is available. Again this blog does not have the intention to fully discuss the axon framework.

Catching and dispatching events on the server

We focus on the event that a new contact is created. The following code shows how you can register a listener for new contacts being created using the Axon framework. The listener makes use of the UpdateMessageProducerForFlex. Axon registers listeners based on the annotation @EventHandler and the argument of the method, in this case the ContactCreatedEvent.

private UpdateMessageProducerForFlex producer;
@EventHandler
public void handleContactCreatedEvent(ContactCreatedEvent event) {
    logger.debug("Received and event with name {} and identifier {}", event.getName(), event.getEventIdentifier());
    ContactDTO contactDTO = new ContactDTO();
    contactDTO.setName(event.getName());
    contactDTO.setUuid(event.getAggregateIdentifier().toString());
    producer.sendContactUpdate(contactDTO);
}

This is the Axon part, the producer is used to send the message to the clients. Sending the message to the flex client is done using the spring BlazeDS integration. The following piece of code shows the producer. After that the configuration of the server.

public class UpdateMessageProducerForFlex {
    private MessageTemplate template;

    @Autowired
    public UpdateMessageProducerForFlex(MessageTemplate template) {
        this.template = template;
    }

    public void sendContactUpdate(final ContactDTO contactDTO) {
        template.send(contactDTO);
    }
}
    <flex:message-destination id="event-bus"/>

    <bean id="defaultMessageTemplate" class="org.springframework.flex.messaging.MessageTemplate">
        <property name="defaultDestination" value="event-bus"/>
    </bean>

The value for the default destination “event-bus” is very important. This needs to be configured in the client as well. Speaking of the client …

Catching and dispatching events at the client

Within the parsley configuration we have configured an object called Consumer. This object configures the client side of the poller and registers it on startup. This registration needs to be done at the right time. Parsleys provides the [Init] notation for that.

The consumer receives messages from the server and dispatches new Notification messages to the client side application. Controller objects pick up these events and update the model. The following code block shows the complete consumer object. Notice the configuration of the ChannelSet and the consumer.

<mx:Object xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script><![CDATA[
        [Init]
        public function init():void {
            consumer.subscribe();
        }

        [MessageDispatcher]
        public var dispatcher:Function;

        public function messageHandler(event:MessageEvent):void {
            if (event.message.body is Contact) {
                dispatcher(new UpdatedContactNotificationMessage(event.message.body as Contact));
            } else if (event.message.body is Address) {
                dispatcher(new UpdatedContactAddressNotificationMessage(event.message.body as Address))
            } else if (event.message.body is Removed) {
                dispatcher(new RemovedItemNotificationMessage(event.message.body as Removed));
            }

        }

        public function faultHandler(event:Event):void {
            dispatcher(new ErrorNotificationMessage("Fault while receiving message" + event.toString()))
        }

        ]]></mx:Script>
    <mx:Consumer id="consumer" destination="event-bus" channelSet="{cs}" message="messageHandler(event)"
                 fault=" faultHandler(event)"/>

    <mx:ChannelSet id="cs">
        <mx:AMFChannel url="messagebroker/pollingamf"/>
    </mx:ChannelSet>

</mx:Object>

Wrapping up

That is it. We have touched all components. I like how clean you can develop flex clients using Parsley and how easy it is to interact with a server using the Spring BlazeDS integration. Also the way to handle events using Axon makes this a very nice way to keep the data in your flex client up to date.

Hope you likes the sample, sources can be found in google code. Check the addressbook sample project of the axon framework.

The following image shows a screendump of the sample, after that are some references.

Screen shot 2010-02-18 at 13.49.13.png
Tagged on:                 

4 thoughts on “Creating a sample for axon using flex and parsley

  • March 11, 2010 at 1:35 pm
    Permalink

    It is possible to use the jetty maven plugin. Step into the flex-ui/war folder and execute mvn clean jetty:run-war

    You might need to do a mvn clean install in the root of the project first.

    I did not notice problems with tomcat so far, but I will re-try it later on.

    Reply
    • September 20, 2010 at 8:25 am
      Permalink

      How do you run the console-ui sample app?

      Reply
  • March 11, 2010 at 8:16 am
    Permalink

    Hi,

    Recently came across your article, I’m new to the concept of CQRS. Still going through the documentation from Axon website. I decided to give the web app a whirl, but I’m getting a ‘Send failed’ error when I try to do anything ( create contact or search ). I copied the WAR in Tomcat webapps dir. Is there something that I missed ? config change etc.

    Thanks

    Reply
    • March 22, 2010 at 1:12 pm
      Permalink

      Hi Guneet,

      which version of the WAR did you download? There were some problems with the initial 0.4 release. Since then, we have deployed a 0.4.2 version, which solves these problems.

      Regards,

      Allard

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>