A Universal Messaging Standard

XMPP will always have a special place in my heart.

XMPP is an open, XML-based protocol for near-real-time messaging and presence. Originally popularised by instant messaging, it provides a general-purpose, federated messaging architecture built around long-lived connections and asynchronous communication. It is massively scalable.

We support more than 2 million concurrent users on a single ejabberd node. At all times, ejabberd health was fine... both TCP connection establishment and authentication performed very fast, in under 10 milliseconds.

— ProcessOne

Its fundamental protocol units are XML stanzas:

  • message — messaging between entities
  • presence — availability and status
  • iq — request/response interactions

XMPP is extensible through XEPs (XMPP Extension Protocols), which add capabilities ranging from service discovery and publish-subscribe to file transfer and multi-user communication.

Beyond client connections, XMPP also supports components: server-side services that connect to an XMPP server and provide additional application functionality.

XMPP is one of the unsung heroes of the messaging world.


HeuCampus

Heucampus... is a system application that allows students to communicate with their peers and their teachers via instant messaging. Students can form groups, share documents, and share screens... working collaboratively in a digital environment.

— UNESCO Case Study: Singapore

My history with XMPP goes back to 2004, in the early days of C#/.NET, when I developed an XMPP component framework at HeuLabs. I had to develop a streaming XML parser that reduced CPU usage from 50% to 4%, and bridge a VC++ IPv6 socket into C# so a Singaporean school could connect with a Japanese one.

It was built from scratch in C#, providing a high-performance, cross-platform framework that abstracted protocol, networking, dispatch and serialization concerns from the developer.

This is an example of simple echo service using the framework:

[Service(Name="echoservice")]
public class Service : JService
{
    public Service(string name) : base(name) {}

    [JabberMethod]
    public string Echo(string text)
    {
        return text;
    }
}

It ultimately powered backend services for HeuCampus, a patented real-time virtual-classroom platform providing messaging, collaboration, whiteboarding, desktop sharing, and many other features.

It was good for its time, but was limited in a number of ways. Automatic argument resolution, for example, was constrained to dates, strings, arrays, datasets and common primitives. Anything more complicated was received as a Base64-encoded string or as a JElement type, and manually deconstructed within the handler.

By 2007, HeuCampus was deployed in over 140 schools internationally and had gained global recognition.

HeuLabs has shown that a local Singaporean company can lead the way in global e-learning innovation. Their focus on real-time collaboration is exactly what is needed for the 21st-century classroom.

— Summary of SiTF Award Judges' Comments (2007)

It was one of the most challenging and rewarding periods of my career. I was developing in C#, Mono and Ubuntu, using push technology on tablet PCs at a time when all five were bursting with momentum. My good friend Rakesh—HeuLabs's CEO at the time—and I were invited by Microsoft to demonstrate HeuCampus live to thousands of developers at the official Singapore launch event for Visual Studio 2005. Really wonderful times.

The HeuCampus system... provides a unique platform for students to collaborate and communicate in real-time. It is the backbone of our digital learning environment, allowing teachers to engage with students more effectively than ever before.

— Microsoft Case Study, Crescent Girls' School (CGS)

Years later, I couldn't resist revisiting the idea.


Revisiting the Framework

Since then, application frameworks have evolved by leaps and bounds.

The objective of Project Velocity was to revisit my old XMPP framework with a more modern application model, bringing ASP.NET Core-style abstractions to XMPP services.

The design emphasised:

  • Composability
  • Low Coupling
  • High Cohesion
  • Simple Evolution

This made it possible to introduce protocol capabilities incrementally through isolated packet types rather than constructing a monolithic XMPP object model.

Design

This was the perfect project for C#. I absolutely love its dynamic capabilities.

For example, in the following factory method, the Ping element and its namespace attribute are added dynamically to the IQ stanza rather than exposed as fixed properties:

public static Iq CreatePing(string server)
{
    dynamic iq = new Iq { To = server };

    iq.Ping[Xl.Xmlns] = Xl.NsPing;

    return iq;
}

producing:

<iq id='e59823e0-96ad-4659-8802-ad9b00edb0f9' type='get' to='aquinas'>
    <ping xmlns='urn:xmpp:ping'/>
</iq>

Eventually Ping would become its own subpacket, but the dynamic representation made it possible to work with arbitrary XML without requiring every element to be modelled first.

The RPC dispatch mechanism was working. Here is an enriched version of the echo controller shown earlier:

[Controller(Name="echo")]
public class EchoController
{
    public MessageDTO Reply(string message, UserDTO user)
        => new MessageDTO { Id = user.Id, Text = $"{user.Name}: {message}" };
}

The framework automatically mapped XMPP elements directly to typed objects during routing.

From within Pidgin's XMPP Console, I could send a message to the component and get a response:

Pidgin1

Velocity inspected the incoming XMPP, created the controller, injected its dependencies, determined the action and its argument types, mapped the request, and invoked the method.

There is no XMPP protocol in sight in the application code.

An optional attribute allows the external action name and return type to be specified explicitly:

[Action(Name="greet", Returns="greeting")]
public MessageDTO Reply(string message, UserDTO user)

The request now uses action="greet" and the response contains type="greeting":

Pidgin12

Many of the framework building blocks were now in place, from packet composition and routing to dependency injection and RPC dispatch.

Pidgin12

Dependency Injection

I liked Java's Spring Context, so I experimented with implementing a lightweight container for Velocity.

Ordinary constructor injection worked as expected:

public UserController(IUserRepository userRepository)
    => _userRepository = userRepository;

Velocity resolved and injected the implementation when constructing the controller.

I also experimented with named resolution:

public UserController(IUserRepository userRepository, IUserRepository legacyUserRepository)
{
    _userRepository = userRepository;
    _legacyUserRepository = legacyUserRepository;
}

The parameter names were used to distinguish between registered implementations.


Client-Side Facades

The same metadata used for dispatch also opened another possibility.

Because Velocity could inspect controllers, actions, argument types and return types, it could generate corresponding client-side facades.

Rather than requiring client developers to construct XMPP packets manually, generated APIs could expose remote services as ordinary application-level methods.

This improved discoverability and helped keep client and server contracts aligned.


Conclusion

Project Velocity came to a halt when I landed a high-pressure Kotlin role.

I still have considerable affection for XMPP, and I would love to work with it again.

Velocity was an opportunity to revisit that technology from a different architectural perspective.

With the improvements to modern .NET, the temptation to return to the project is almost irresistible.

In fact, I briefly started again with Velocity. The newer design used channels and pipelines internally, with a hosting model built around dependency injection and middleware:

var service = XmppService.CreateBuilder(args)
    .UseTcpTransport()
    .UseXmpp()
    .AddSingleton<IAuthorizationProvider, StubAuthorizationProvider>()
    .UseMiddleware<ExceptionHandlingMiddleware>()
    .UseMiddleware<LoggingMiddleware>()
    .UseMiddleware<RouterMiddleware>()
    .UseMiddleware<AuthorizationMiddleware>()
    .UseMiddleware<EndpointMiddleware>()
    .Build();

await service.RunAsync();

Until I caught myself.