Monday, October 12, 2009

How to make ejabberd cluster setup a bit easier

Even though there is a decent official documentation and many excellent posts on the topic of setting ejabberd cluster, it still could be confusing, as it was for me. It is possible to avoid some guessing work though and make things more "automated". Following explanation uses the directory structure of installation created by standard ejabberd binary installer:

1. Install ejabberd on a single node
2. Locate ejabberdctl.cfg in conf directory of your installation and adjust Erlang node name on the last line:

ERLANG_NODE=ejabberd@`hostname -f`  
 
3. Run ejabberd:
 your_ejabberd_dir/bin/ejabberdctl start
4. Make sure it runs:
 your_ejabberd_dir/bin/ejabberdctl status 
 By now you're done with first node.  
For the second and consequent nodes:
5. Synchronize node's Erlang cookie with one at 1st node (check out "Clustering setup" in ejabberd documentation on how it's done); 
6. repeat steps 1 to 4; 
7. Run:
 your_ejabberd_dir/bin/ejabberdctl debug 
At Erlang shell prompt (press Enter when asked "press any key"), type:
FirstNode = 'ejabberd@first', %% use the name of your first node (ejabberd@, see p.2 above)
mnesia:stop(),
mnesia:delete_schema([node()]),
mnesia:start(),
mnesia:change_config(extra_db_nodes, [FirstNode]),
mnesia:change_table_copy_type(schema, node(), disc_copies). 
 

The above script is a replacement of p.2,3 of official ejabberd clustering setup doc. It takes advantage of not having to manually figure out Mnesia location and proper syntax of the command suggested in there. 
8. End debug session by pressing Ctrl-c, Ctrl-c;
9. Continue with p.4 of official ejabberd clustering setup document. 
 
The piece of code above could probably be useful elsewhere, for example as part of ejabberd admin interface. Imagine having "Join cluster" and "Leave cluster" buttons somewhere on a Nodes page. Also, it's probably possible to save some manual work in situations where you want a bunch of ejabberd nodes to join existing cluster. In this case you could wrap something similar to above code into a single function and do rpc call on each of these nodes. All such things would obviously require a bit more work, such as checking if the running node is already part of the cluster etc.

Friday, September 18, 2009

Erlang for Java programmer - Java can be good at times, too

Peace and let's say no to flame wars, comrades! Don't get me wrong, Java is an awesome language, but if you have already started serious coding in Erlang, you will probably never look back. Or maybe you will occasionally, like I did few days back, when I was charged with the task of cloning ejabberd module by replacing mnesia with odbc calls.
It might sound like fairly routine task, and it was for the most part, except sometimes it's hard to reason about Erlang function output just by looking at the source code. Java methods, on the other hand, always have statically typed output, which obviously eliminates any guessing work. So, after few attempts trying to mentally trace some of mnesia transactions, I suddenly felt my brain overloaded and had to resort to debugging ejabberd from remote node :-) I had to recompile the module with -compile(export_all) in order to be able to try functions that contained mnesia code in Erlang shell and figure out their exact output. This, of course, is an intrusive action and assumes the source code is available.
It could have been much easier, though, if the code was accompanied by documentation markup. Having function specifications will hugely help anyone who will ever have to work with the code, including the owner. I'm now ready to make resolution to have specifications for every single function in my code, including non-API functions. By the way, exmpp team really impressed me by thoroughly following this path throughout entire code base.
To me, having to have function specs in the code, while being a good thing on its own, is a very small price for the feeling of liberation and increased performance that dynamic typing in Erlang gives you compared to a static one in Java.
In conclusion, check out much more comprehensive discussion of typing problem in Erlang.

Monday, September 7, 2009

XMPP web demo: setting it up

I will describe the XMPP demo setup on AWS EC2, starting with Alestic Ubuntu karmic image (ami-19a34270). Hopefully, you should be able to adjust the process to your environment of choice.

Setting up

Brace yourself, that's going to be a long one. Major components to install and configure are:
  • Erlang environment
  • exmpp
  • ejabberd
  • nginx
  • and, of course, weazard itself
So, provided you have running instance of ami-19a34270, let's  ssh to it and start by:

sudo apt-get update && sudo apt-get upgrade

We'll need svn to checkout some stuff later:


apt-get install subversion


Then install Erlang (version R12B5 or newer; karmic has R13B01):
 

apt-get install erlang

Now, let's move on to exmpp.  First, we need to install some libraries that exmpp is using for XML support:

apt-get install libxml2-dev

or, alternatively:

apt-get install libexpat1-dev


Update: the demo will work fine with libxml2, however, at the time of writing (Nov 07, 2009), if you're planning to use exmpp for other purposes, particularly, exmpp_session module, you're advised to install libexpat1-dev instead. See https://support.process-one.net/si/jira.issueviews:issue-html/EXMPP-16/EXMPP-16.html for details.

apt-get install libssl-dev
apt-get install pkg-config


Now, download the exmpp distribution and build:

wget http://download.process-one.net/exmpp/exmpp-0.9.1-r857.tar.gz
gunzip exmpp-0.9.1-r857.tar.gz
tar -xvf exmpp-0.9.1-r857.tar 
./configure
make
sudo make install


At this point you may want to check if exmpp is in working condition. Start erl and at the prompt type

1>exmpp:start().
ok
2>exmpp_xml:start_parser().

If response is similar to
{xml_parser,[{max_size,infinity},
             {root_depth,0},
             {names_as_atom,true},
             {emit_endtag,false}],
            #Port<0.747>},


then everything went fine and you can type q(). at the prompt to close Erlang shell.

Next, installing ejabberd. We need to add a Jabber domain name to /etc/hosts first.

The first line in /etc/hosts after editing will look something like (substitute zephyr to anything you like):

127.0.0.1 localhost.localdomain localhost zephyr.local

Note: You will be using this domain name in many places, so please make note of it; I will be using zephyr.local in this post going forward.

Download, unpack and launch ejabberd installer:

wget http://www.process-one.net/downloads/ejabberd/2.0.5/ejabberd-2.0.5-linux-x86-installer.bin.gz
gunzip ejabberd-2.0.5-linux-x86-installer.bin.gz
chmod 755 ejabberd-2.0.5-linux-x86-installer.bin
./ejabberd-2.0.5-linux-x86-installer.bin

Make default choices during installation; when asked about ejabberd server domain, enter host name you previously added to /etc/hosts (i.e. zephyr.local). Note default installation directory ( /opt/ejabberd-2.0.5 by default), you will need it shortly.

Once installation is completed, edit ejabberdctl.cfg by changing the very last line from:

ERLANG_NODE=ejabberd@localhost
to
ERLANG_NODE=ejabberd@zephyr.local

This is an important change, especially if you're planning to reuse your EC2 instance; I will omit the explanation for now.

Now, let's make sure ejabberd is good:

cd /opt/ejabberd-2.0.5/bin
./ejabberdctl start
./ejabberdctl status

You should see:
Node 'ejabberd@zephyr.local' is started. Status: started
ejabberd is running



Next step - configure external XMPP component on ejabberd side.
Open up ejabberd.cfg in /opt/ejabberd-2.0.5/conf with your editor (you may want to make a backup copy first), locate the line starting with {5269, ejabberd_s2s_in
and add service description after the closing curly bracket:

  {5269, ejabberd_s2s_in, [
                           {shaper, s2s_shaper},
                           {max_stanza_size, 131072}
                          ]},

   {7047, ejabberd_service,
   [{hosts, ["test1.zephyr.local"],
         [{password, "secret"}]}]},


  %%
  %% ejabberd_service: Interact with external components (transports...)
  %%


Save ejabberd.cfg and restart ejabberd:

/opt/ejabberd-2.0.5/bin/ejabberdctl restart
/opt/ejabbberd-2.0.5/bin/ejabberd status

Provided ejabberd has restarted successfully, let's create a bot account (it's curently being used to help with weazard registration):

/opt/ejabberd-2.0.5/bin/ejabberd register _weazard_bot zephyr.local weazard

We're done with ejabberd for now. Time to checkout our demo code:

mkdir weazard
cd weazard

svn co https://tagsahead.svn.beanstalkapp.com/erltwit/trunk/web
svn co https://tagsahead.svn.beanstalkapp.com/xmpp_component/trunk

Review config.js in web/js and adjust it, if needed.

Review weazard.app in xmpp_component/trunk/ebin and adjust it.

We will use nginx as our web server:

apt-get install nginx

Below is configuration fragment that has to be included (directly to /etc/nginx/nginx.conf or through include  directive):

server {
        listen 8000;
        root /home/nginx/web/;

        location /http-bind {
                proxy_pass http://localhost:5280/http-bind/;
        }

}

We'll need to move our web application to the place where nginx can access it, i.e. /home/nginx/web
The highlighted piece is necessary because due to Javascript security Strophe can't directly reach ejabberd's HTTP binding service.

 Update: Starting ejabberd v 2.1.3 configuring proxy is not necessary, so you won't have to include the highlighted text above. You might still want to configure the proxy if your web clients are behind firewall with the BOSH port (default 5280) disabled. See http://rfid-ale.blogspot.com/2010/04/end-of-cross-domain-hassle-for-bosh.html for details.


We're done!

Running demo
Start ejabberd:
/opt/ejabberd-2.0.5/ejabberdctl start (or restart, if it's already running);
Start nginx:
sudo /etc/init.d/nginx start (or restart, if it's already running);

Start weazard XMPP component:
cd  ~/weazard/xmpp_component/ebin
erl -sname main -setcookie weazard

At the prompt, type:
1>application:start(weazard).

If you see the line:
Component : "test1.zephyr.local" started.

in the output,  then weazard has managed to connect to ejabberd and is ready to push data to subscribers.

You're now ready to launch your browser and try:

http://yourhostname:8000/weazard.html

Can I congratulate you with running demo? I hope so, but if you are not there yet, fear not!  As you can see, a lot of things can potentially go wrong. There's plenty of resources on configuring ejabberd/BOSH/nginx, and I'm here to help to the best of my abilities.

Thursday, August 27, 2009

XMPP web demo update

Yesterday was a big day, I got over 100 visitors on my blog, and many of you have registered and tried the demo. Was it because of me posting about demo on identi.ca and metajack.im? Anyway, thank you all for taking time and trying, I have gotten strong motivation to keep up with XMPP topic because of your interest.

Couple of things that I wanted to mention: first, some of newly registered users seemed to drop out really quick. I'm guessing that was because they weren't able to see much action from the demo. I should have explained that if you have small number of subscriptions, chances are you'd have to wait for few minutes before seeing any data update. That's because the update generator chooses one station at a time every 15 seconds, so if you have only couple stations chosen, it'd take in average about 2 mins for updates to show up, given there are currently 17 stations total. So the hint is, if you want a real entertainment, subscribe at least to 8-9 stations, then you'll see it all rolling full speed.
There are also few cases where people dropped out not even finishing registration (initial greeting from Jabber still sits there for such accounts). I would be curious to know, what had happened there? Was it a slow script, browser crash or something else? I'd appreciate any feedback you could give.
And speaking about feedback, you can now easily send a message to a friendly support team (that would be me) from within demo! Just click the button on Support panel and submit the 2-field form, I'll be sure to read and respond. Note that all this done with Javascript one-liner, I didn't have to touch any of server code, all that goodness given for free by ejabberd and Strophe.

Wednesday, August 26, 2009

XMPP web project walk-through

Now that the XMPP demo is online and pretty stable, I will try to explain what is it actually doing. Should have done it at the time of putting it online, but it's never too late to do a right thing.

The objective.

The objective of demo is to show how information can be delivered to browser clients in real time using XMPP/BOSH technique.

The functionality.

The demo application gives prospective clients ability to subscribe to one or more (imaginary) weather stations and receive (made-up) weather alerts in real-time. The client can subscribe/unsubscribe to/from any of available weather stations. The subscription will be associated with client's account and used for the future sessions, so the client is required to register account first time she's using the demo.
NOTE: You may use existing shared account (username: shared, password: shared) in case you do not wish to register. Registering is safe, though, as it creates a Jabber account on local domain, which is not accessible from outside. If you decide to use shared account, expect other people to alter your subscriptions. You may actually watch someone turning your subscriptions on and off, which represents a nice case of real-time collaboration :-)

Visual representation and controls.

Right-hand panel contains weather station locations organized as country -> region -> city hierarchy. The icon next to station location denotes its subscription status, as follows:



- client is not subscribed to the station;

- client is subscribed to the station;

- the request to turn subscription on/off has been sent to the server.


Clicking on station name will send request to turn subscription on/off for the logged in client.

Middle panel contains weather updates, one per subscribed station. Once update is received from the station, corresponding section will show up. Only last update is shown. The client can click on any section to unfold detailed information about last update received from the station.


The moving parts (architecture, that is :-).

On the client side we have HTML page that employs Strophe.js library. Strophe provides communication with XMPP server using BOSH protocol.

On the server side:
  • weazard - Erlang application that acts as XMPP component, using gen_component implementation; it exposes weather stations for the service discovery and manages subscription requests by routing weather updates to subscribers;

  • weather update generator - generates weather alerts every 15 seconds by randomly choosing weather station, temperature and condition.

The flow.

The client signs in, triggering Strophe to start service discovery procedure by sending "disco" requests to weazard in order to find out what weather stations are available. Strophe then receives "disco" responses through its callbacks and builds the treeview that reflects weazard's service structure (i.e. country->region->city hierarchy that resides on the right-hand panel). After the treeview has been finished, Strophe advertises client's presence, which in turn triggers XMPP server to route presence to all client's subscribed stations. As stations' addressing includes weazard's JID as host, weazard takes over and sends presence from each of subscribed stations back to Strophe. Strophe then uses presence callbacks to show subscription state (all those red and green "transmission" icons in right-hand panel and message sections in middle panel) for all discovered stations in the client's view.
From this moment on, Strophe listens to weather alert messages and updates corresponding sections in the middle panel as they go.
On the server side, weazard receives alerts from weather update generator, turns them into XMPP messages and routes them to subscribers via XMPP server.


Advantages of using XMPP/BOSH web vs. traditional web frameworks.

The most obvious advantage is a real-time interaction. This doesn't directly have to do with XMPP, but rather is provided by BOSH protocol. Very similar techniques have been around for quite some time, for instance, Comet or pushlets. BOSH is currently supported by most well-known XMPP server, which makes it natural to use with XMPP.
Another important thing is ability to reuse XMPP server features. For instance, there is no need to have separate database of registered users and their subscriptions, as this is already maintained by XMPP server. All changes in user's presence (i.e. logged in/out, disconnected) and component presence (restart, disconnection etc.) are also taken care of. It is possible to easily implement IM features, such as live support or chat between users.
XMPP/BOSH also enables asynchronous messaging style. This means that the code that submits the request will never block on waiting for the response; instead the response will be handled by a callback function. In practical terms, this makes web application more responsive and user friendly. Technically, the same behavior can be implemented without XMPP, it's just that asynchronous messaging is a natural way of life for XMPP, as opposed to a web server that is more accustomed to sequential "request-response" interactions.


Word of caution.

Although the demo has some things done right, in case you decide to reuse the code, you should be aware that the approach being used for serving subscriptions is not suitable for every situation. Let me explain: if 500 clients will subscribe to a single weather station, the component will send 500 nearly identical XML messages to the Jabber server every time the update happens. This is not good, and should rather be implemented with PubSub or MUC. If, on the other hand, you have one-to-one subscription (say, client wants to get updates from her Twitter account), then you're fine.

I will keep fixing bugs and adding new features to the demo. Your feedback is most welcomed.




Sunday, August 23, 2009

Intermission: Erlang production nirvana

Well, almost. The XMPP Web demo project, despite using some of the best tools available today, is still a typical software project with bugs popping up here and there. Since it went on air couple of days back, one of the server-side components kept going down after few hours of up-time. The Erlang node it was running on would suddenly cease to exist without a sound or even a crash dump left anywhere. The cause is not yet completely clear, and it's going to take some time to figure out and fix.
This would probably be a show-stopper for any production system I had ever encountered. Not for the system made with Erlang! While I'm working on the bug, I'd like to keep my demo up and running without having to monitor that failing component, so here's how:
  • The node that runs component in question will be started from master node using slave:start;
  • Once the node is started, the component application will be launched using rpc:call
  • The node will be monitored with erlang:monitor_node; in case monitor process detects node going down, we'll repeat it all again.
The code is only a few lines long. It works with packaged application, but can be easily adapted to Module:Function form. Note that there is a stop_node/2 function, in case you do want to stop the application without exiting your Erlang shell.
"Normal" crashes (the ones that don't bring node down) have been already taken care of with Erlang supervisor. So nothing can now stop you from trying the demo any time of day :-)

Update: I should have mentioned that another option to keep your node up is to use -heart switch of erl command. Coding solution allows more control though, for instance you may want to be able to run some checks before restarting node, whereas -heart will restart unconditionally.

Friday, August 21, 2009

Real-time Web applications with XMPP: working demo

Yes, this is a mid-size demo web app with XMPP, more specifically, combination of Strophe, exmpp, ejabberd on Amazon EC2. The purpose of this exercise was to create a "proof of concept" and prepare myself for more serious projects. The demo is now available online , go ahead and check it out. You could either use predefined account (username: acc1, password : acc1), or register your own. Choose weather stations from right-hand panel and watch your browser being updated in a real time with data coming from (fake) weather service. Clicking on station's icon will toggle subscription, green arrow icon meaning the account is currently subscribed to that station. You will also see (normally quickly passing) blue icon meaning station is waiting for updated subscription status from the server. The demo is in early beta (if I can say this about demo at all), some features, such as live network status and live support are coming soon.



The app is very dynamic. Try this: open 2-3 windows with the same account and watch subscription changes you're making in one window instantly showing up in others. This is a nice feature that could be used for real-time collaboration. It's possible to imagine a number of applications that need real-time updates and collaborations of that sort: trader floors, stock tickers, asset tracking, order fulfillment, arrivals/departures, ticket reservations, chats, tweets, you name it.
It's not surprising that Google have chosen XMPP as core protocol for the Google Wave.
What's the big deal, you might ask? We have been using dynamic Ajax-based apps for a while now. This is a better Ajax, though, as it uses XMPP over HTTP (BOSH) protocol. You can read about it in details here and here. Briefly, most Ajax applications are using polling, which is poorly scalable and resource-hungry technique. Javascript libraries that employ BOSH, on the other hand, are using Ajax with "semi-permanent" connections, keeping them alive until server really has data to send to the client.
And, with Javascript libraries supporting BOSH, such as Strophe or jsJac, plus JQuery, "We Don't Need No Stinkin' Web Frameworks" :-)

The XMPP-specific features having been implemented/used in the demo:
  • in-band registration;
  • service discovery;
  • roster push handlng;
  • presence notifications and subscription handling.
In the next few days, I'll go over setting all things up on EC2, which is no small feat, at least it wasn't for me :-) I'm also planning to add some more features, such as self-restoring after disconnections and crashes, both on server and browser sides. As usual, your input is appreciated and welcomed.

The source (HTML/JS/Erlang) is available here.

P.S. Please remember this is a fake data, so don't use it for planning your weekends, although I have taken care not to show "Heavy Snow" icon during summer. I might be not that far off from official weather feeds, though :-) One day I might just take a real feed and turn that demo into something useful :-)

Saturday, July 11, 2009

XMPP gen_component update: Design and features

For the last few weeks I've been trying to put together a framework for creating XMPP components in Erlang. While the code I have now is not by any means covering all possible features the XMPP developer might need, I think it's a good starting point. I'm going to go over the interface and features of gen_component behavior that represents framework's API. Your input is very much appreciated.

gen_component implementation is backed by gen_fsm, so just like for gen_fsm, there is a certain set of callback functions that must be provided by a callback module.
XMPP (external) component could be seen as intermediary between XMPP server that routes messages, and some backend service that needs to be exposed to XMPP server's clients. The service could be anything: a database, a game, a stock ticker, Twitter feeds etc. Hence there are functions handling XMPP server conversations, such as handle_iq/4, handle_presence/4 and handle_message/4, and the functions handling interactions with backend service, such as feed_component/2 and handle_feed/2. XMPP service discovery support for the component is backed by functions disco_info/2 and disco_items/2. There are also functions that deal with life cycle of gen_component, such as start_component/6, stop_component/1 and their respective callbacks init/2 and terminate/1. Interface includs some helper functions such as send_packet/2 and print_packet/1.

As a proof of concept I built a test_component module that implements gen_component callbacks. test_component represents mocked weather alert service Jabber clients could subscribe to. The client browses available weather alerts and registers with ones he wishes to get updated with. That's how it looks like in action:

exmpp library has been used extensively in gen_component, and I found it pretty stable, although there were some gotchas that I attribute to my lack of experience. I'm planning to do a detailed code walkthrough with comments on exmpp usage.

Source code for gen_component is here.


Friday, July 3, 2009

gen_component project: service discovery with exmpp

The gen_component behavior is growing up and is making more sense to me as the XMPP component project is moving along. I can even see gen_component rivaling gen_server in the nearest future :-). Jokes aside, it will have handle_iq, handle_message, handle_presence and possibly more callbacks with helpers glueing it all together, in a fashion similar to OTP behaviors.
By the way, the component doesn't even have to be a process, depending on what exactly your component does. So far I managed to avoid maintaining conversational state, but will have to introduce it going forward. For instance, to tell the registered user from a new one, having state seems to be a must, doesn't it? Still, should I use Mnesia for it, rather than process state?

The project is now having service discovery written using exmpp. The imaginary weather alert service exposes itself through disco_info/1 and disco_items/1 (which are now part of gen_component).

That's how it looks like in Psi:


As you can see, there are two weather alerts running, each one having few nodes for specific weather alerts. Are you thinking of stock tickers, twits etc. ? Yes, everything is possible with gen_component and XMPP :-)

Check out the source code and instructions.
Next in line is in-band registration.
I'll be doing web interface with Strophe or jsJac as well once I have a chance.
I would be interested in your thoughts and ideas on gen_component.

Writing XMPP components in Erlang with exmpp

I'm going to show how to create an external XMPP component in Erlang. If you're in a hurry and familiar with ejabberd, you may want to go straight to the source code.
This mini-project started when I decided to build XMPP interface for the system that had already been completed. I chose external component over ejabberd module, because I wanted to avoid ejabberd dependencies and be able to use it with any Jabber server. As the system was written in Erlang, and I had already written gen_mods, Epeios seemed to be an obvious way to go, as it promotes turning standard ejabberd modules into XMPP components. Life turned out to be not so rosy though, after I had realized that module development in Epeios is deprived of features that makes writing gen_mods so convenient, namely hooks and handlers. Epeios site states that they are working on it, but for now you're totally on your own when it comes to handling all traffic routed to the component. Epeios still handles XEP-0114 connection protocol, but this alone wasn't enough to justify its usage.

So I decided to create a module with XEP-0114 implementation by borrowing some code from xmpp_component.erl in Epeios distribution.
The Epeios code (version 1.0.0) implies that:
  • the component has start/2 and stop/1, which is what gen_mod behavior requires;
  • start/2 creates a process and returns {ok, Pid}, which is a deviation from official ejabberd module development doc;
  • the process created by start/2 has to be able to handle messages of form {route, JID_From, JID_To, Packet};
  • to send a stanza on behalf of component, the module calls xmpp_component:send_to_server/1;
So it appears that in order to use standard ejabberd modules in Epeios, one should wrap them in a way that satisfies above requirements. As my goal was just ripping out some code pieces rather than reusing the whole thing, I felt that following gen_mod wouldn't bring much value, instead I wanted a behavior that would aid component writing in a more structured way.
Hence the gen_component behavior was created...
-module(gen_component).

-export([behaviour_info/1]).
-export([send_packet/1, component_name/0]).
% Return a list of required functions and their arity
behaviour_info(callbacks) ->
[{receive_packet, 3}, {send_packet, 1},
{component_name, 0}];
behaviour_info(_Other) ->
undefined.

% Helpers
send_packet(XmlString) ->
xep0114:send_to_server(XmlString).

component_name() ->
xep0114:component_name().

...and the code in xmpp_component.erl was altered accordingly in order to replace gen_mod by gen_component.

This left me with only one dependency - xmpp_component:start_xml_parser/1 directly loads platform-dependent expat_erl.so driver, location of which has to be defined in Epeios configuration file. I didn't want to tweak configuration and remember to choose suitable driver every time the component is being deployed.
Recently released exmpp library came to rescue. The exmpp build script automatically resolves the dependency on linked drivers that are used by XML parser. However, the interface of exmpp XML parser is significantly different from the ejabberd one that Epeios is using. While I was preparing myself for a next round of tweaking, I found that exmpp team had already taken care of this problem by providing adapters (in src/compat directory) that allow client code to use ejabberd XML parser interface with exmpp behind it. After some more coding I had a minimal component that was visible to external clients upon connection to ejabberd and could send and receive XMPP packets routed by ejabberd.

References:

Your comments and questions are welcome.


Update, July 2, 2009: Revamped the code to add more exmpp stuff; the framework now supports multiple components running on the same node (previously it was only possible to run a single component). Next will add service discovery support. exmpp appears to be quite handy, many helpers etc. Need to spend more time with it, though - lack of docs and comprehensive examples slows me down considerably.

Tuesday, June 23, 2009

Erlang for Java programmer - Design patterns, p.1

The Gamma et al. "Design patterns" book formulates common desing patterns that could be applied to any object-oriented language. When we move to functional language domain though, many OO design patterns simply don't apply. This is hardly surprising, because functional programmers by definition don't do OO design. Some of the problems that OO design patterns are supposed to solve still have their counterparts in functional programming.

We are going to walk through the book's pattern catalog and try to show a matching Erlang approach for the OO patterns, where possible.

Going forward, I will be referring to "Design patterns" book as DP. In (unlikely) case you'll want to follow, it'll be helpful to have this book handy.




Adapter (Wrapper)



Intention(DP): Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.



Let's just substitute "class" to "function", and we'll have it for Erlang:



Convert the interface of a function into another interface clients expect. Adapter lets functions work together that couldn't otherwise because of incompatible interfaces.



Now, function's interface obviously being its arguments, in most cases we can easily create adapter for that function, which is another function having desired set of arguments.



Example:



Trying to stick closely to DP example, let's assume that we are working on drawing editor and want to implement text drawing capabilities in module textshape. The behavior that we have decided to use for all shape kinds contains bounding_box/1 function that returns bottom left and top right coordinates for any shape. So we are now concerned with task of writing bounding_box() flavour for text shapes. We have 3rd party module textview at our disposal, and we want to reuse it for our purpose. It happens that textview exports (in Java terms, has public) functions:



get_origin(TextView) that returns origin point of the text view in form of tuple {X, Y}, and

get_extent(TextView) that returns width and height of the text view in form of tuple {Width, Height}.



Our bounding_box/1 function then might look as follows:




# Calculates bounding box for text drawing;
# returns tuple {TopLeftX, TopLeftY, BottomRightX, BottomRightY}




bounding_box(TextShape) ->
TextView = convert_to_textview(TextShape),
{X, Y} = textview:get_origin(TextView),
{Width, Height} = textview:get_extent(TextView),
{X, Y, X + Width, Y + Height).









What about convert_to_textview/1 function? Not to worry, it should be easy. Remember, TextView and TextShape are not objects, but rather data structures, so conversion is normally just a matter of knowing what these structures are and matching parts of them. Below is a sample implementation, just to give an idea:




# In this case, we assume that TextShape is a list of properties, such as:
# [{text, "Desing patterns"}, {font, "Arial"}, {color, "red"}, .....];
# TextView is a dictionary with keys viewtext, viewfont, viewcolor, viewstyle etc.




convert_to_textview(TextShape) ->
lists:foldl(fun(Prop, Acc) ->
{Key, Value} = convert_prop(Prop),
case Key of
not_present ->
Acc;
_Other ->
dict:store(Key, Value, Acc)
end
end, dict:new(), TextShape).


convert_prop({"text", Value}) ->
{"viewtext", Value};
convert_prop({"font", Value}) ->
{"viewfont", Value};
%% Conversions for other properties
...................................
%% Properties that don't have counterparts in TextView structure
convert_prop({_, _}) -> not_present.










That's it, we have Adapter implementation blueprint for Erlang. Looks kinda trivial, don't you think? That's what you're gonna like once you start Erlang coding - many things are easier and definitely less verbose compared to Java.

Thursday, June 18, 2009

Erlang for Java programmer - power of functions

I have been using Java for more than 10 years now, and at the time I had come across Erlang, while I was excited about what Erlang has to offer, I was a bit worried on how to survive in absence of beloved OO features, such as abstract classes, polymorphism, inheritance etc. It turned out that these questions were somewhat irrelevant, instead I should have asked:



How the problems that OOP (Java, in particular) solves using feature "X" can be solved by functional language (Erlang)?




Polymorhysm vs. Higher-order functions



I'd like to show a practical example where Java programmer would use polymorphism, and Erlang programmer would use higher-order functions.



One of my Jabber/XMPP projects required message filtering based on some set of conditions. The customer wanted to implement some kind of censorship for their chat rooms. If incoming message fails any of those conditions, it has to be dropped. Let's assume that for this task we will maintain a list of pairs {condition, text}, where condition is a boolean string function that applies to the message text, for example the list could look like:

{"contains", "@$#*%!"}, {"equals", "This chatroom sucks"}, {"contains", "morons"}

etc.



So to pass the "appropriateness" test, the incoming message should fail each condition in a condition list.

Now, let's limit ourseleves to 2 functions (contains and equals) and see how we go about coding this in Java:




package com.teamhand.messaging.utils;



import java.util.ArrayList;

import java.util.List;



public class Censorship {

List <Condition> conditions = new ArrayList<Condition<();

public Censorship() {

super();

}



public void addCondition(Condition condition) {

conditions.add(condition);

}



public boolean checkMessage(String message) {

for (Condition condition : conditions) {

if (condition.check(message)) return false; // message has been censored

}

return true; // message has passed the censorship

}



public static void main(String[] args) {

ContainsCondition c1 = new ContainsCondition("@$#*%!");

EqualsCondition c2 = new EqualsCondition("This chatroom sucks");

ContainsCondition c3 = new ContainsCondition("morons");



Censorship c = new Censorship();

c.addCondition(c1);

c.addCondition(c2);

c.addCondition(c3);

String message1 = "I'm a good message";

String message2 = "Are you gonna listen, you morons?";

System.out.println(c.checkMessage(message1));

System.out.println(c.checkMessage(message2));

}

}



// Condition class

package com.teamhand.messaging.utils;



public abstract class Condition {

public abstract boolean check(String message);

}



//Implementation classes

//

//

package com.teamhand.messaging.utils;



// ContainsCondition class

public class ContainsCondition extends Condition {

private String template;

public ContainsCondition(String template) {

this.template = template;

}



@Override

public boolean check(String message) {

return message.contains(template);

}



}



// EqualsCondition class

package com.teamhand.messaging.utils;



public class EqualsCondition extends Condition{



private String template;



public EqualsCondition(String template) {

this.template = template;

}



public boolean check(String message) {

return message.equals(template);

}



}









The implementation is typical - we have a polymorphic list of Condition instances, whose classes implement condition-specific check() method.



Now, it's Erlang's turn:




-module(censorship).

-export([check_message/2, test/0]).



condition({"contains", Phrase}) ->

fun(MsgBody) ->

string:str(MsgBody, Phrase) > 0

end;



condition({"equals", Phrase}) ->

fun(MsgBody) ->

(MsgBody == Phrase)

end;



condition(Other) ->

throw({error, {not_supported, Other}}).



%% Returns false is message should be censored, true otherwise

check_message(Message, Conditions) ->

not lists:any(fun(Condition) ->

Condition(Message) end, Conditions).



test() ->

C1 = condition({"contains", "@$#*%!"}),

C2 = condition({"equals", "This chatroom sucks"}),

C3 = condition({"contains", "morons"}),

CondList = [C1, C2, C3],

{check_message("I'm a good message", CondList), check_message("Are you gonna listen, you morons?", CondList)}.





Note how condition/1 function returns another function, so in check_message/2 we have a list of functions, each one representing a condition. As you may have expected, the solutions are very similar, except the Erlang one bears significantly less code.



To conclude, this post is not to start another flame war on merits of languages, but rather for those OOP programmers who have decided to try Erlang and may need some help in finding common ground while exploring functional style.