Saturday, November 27, 2010

Sip Servlets Application Routing Guidelines and Best Practices

Sip Servlets 1.1 enjoys great adoption in the telco industry displacing proprietary and legacy applications. One of the big promises of Sip Servlets 1.1 is the application (WAR module) composition and isolation provided by the Application Routing feature which is supposed to allow the applications to grow over time independently from each other and orchestrated in different services.

On the surface...

Application Routing is probably one of the most misunderstood parts of the specification. Misunderstood not in terms of function, but in terms of use and consequences. From JSR-289 application routing is described as a separation of concern concept similar to the Chain of Responsibility Design Pattern where each application is responsible for it's own small part of the service and can be added or removed on demand. In fact it would often look like a pipeline on your architecture diagrams:
  • Call Blocking will check if the Callee has blocked the Caller and if so it will reject the call. Otherwise if the the Caller is not blocked it will just proxy the call to the next app.
  • Call Forwarding will forward the call to the Callee or her voicemail depending on availability status.
  • The Voicemail application (if reached) will act on behalf of the Callee and it will answer the call to record a message.
Looking great - clear responsibilities for each application, zero coupling, applications are completely unaware of each other, ..blah, blah, blah. Every architect's dream!

The Real World Picture

What is not shown in these diagrams however is how it works inside the container with all external entities. JSR-289 mandates that applications communicate with each other through SIP, which means all messages go up and down the stack to reach the next application. Internally, Mobicents and most Sip Servlets containers optimize the message passing by skipping a few stages of the SIP protocol stack, but still you have to do it. And that's not the problem at all. There are a number of things from the real world that are missing in the picture:
Each application creates it's own application session. The B2BUA application creates two Sip Sessions while the Proxy and the UAS application create one session each. Additionally if the container operates with dialogs it will create 2, 3 or 4 dialog representations. Sip Servlets applications have no awareness of the SIP dialogs and have no references to dialogs directly but they are in the memory. The Sip Servlets specification says that dialogs roughly correspond to SipSessions, which is true from application point of view, but in SIP terms the dialog spans from UAC to UAS through any proxies in the way, thus in the best case it is possible two or more sessions to share the same dialog instance if they are on the same machine.

So what's the big deal? The applications care only about their own state and not the other application's state, right? Not exactly. To maintain the call internally, the containers keep references to a number of objects that represent the transactions or the the application routing chain in some way. SipApplicationSessions have timers associated with them and so do the SIP client and server transactions. B2BUA and proxy implicitly have to keep the associated inbound and outbound requests/responses. There are also a number of properties that users can specify and are maintained in memory. That's just for empty sessions. Once the developers start adding session attributes independently for each app, it is almost certain that they will have duplicate data.

In real-world applications most of the data would be somewhere in persistent storage such as a database. The data would be queried and loaded in the sessions by the applications. Because the applications can't share state they will have to query the database independently and very likely transfer and use duplicate data like the user profiles and preferences. Note that I am not assuming that it has to be that way. You may be able to organize the data and the queries not to transfer redundant data, but this is very hard to do correctly over time and especially when you need to have separate Web UI to access the data. It is just better to make one network request than many.

On top of that if you took advantage of the application router properly it may also query the database, especially since it is responsible for assigning subscriber identity and function selection.

To summarize what we got so far for a single call in this 3-app service:
  • 4 times SipSession with attributes and properties
  • 3 times SipApplicationSession with attributes and properties
  • 2 times SIPDialog
  • Whatever transactions are in progress multiplied by at least 3 (client and server)
  • 3 times the timers
  • 3 times JDBC over the network, once for each app, each in separate DB transaction
  • 3 times call the getNextApplication() to the application router, each potentially querying the database again
  • ..and I am probably forgetting something
Fault tolerance

When you account for the fault-tolerance, you need to have at least one replica of the above state. Replication occurs all the time continuously and has very serious additional consequences for memory, network traffic and CPU utilization. Application Routing also amplifies any gaps in time where a failure is unrecoverable depending on the replication policy.
In the end, with or without fault tolerance, having this service implemented in 3 applications probably costs around 3 times more in terms of consumed hardware, development/testing and has much slower response time compared to a monolithic application that does the same with simple if .. then .. else statements. Application chaining doesn't look like such a good idea any more.

Converged HTTP applications spaghetti

Unlike SIP, HTTP servlet applications do not allow composition. Usually each SIP application would have some sort of Web UI in the same WAR module allowing users and administrators to configure the system or user profiles. Do you really want your service to have 3 different entry-points of configuration? Block users from one app, configure forwarding in another and set the voicemail greeting in a third app. One option would be to use a Java Portal (JSR-168 or JSR-286 with WSRP) portlets to combine the UI. That may or may not work. I am not going to cover the limitations of Java Portals, but the fact is that it has some limitations, greater complexity, performance penalty and so on. If you turn on and off application from the application router, your application router will have to update the portal configuration to reconfigure the UI.

In most cases you will have a separate Web UI application that is aware of the schema used by all 3 applications, which limits the potential of the SIP applications to grow independently from each other. A modification in one app may force a change in your Web UI app.

No matter which way you go, there will be a chain of dependencies that your applications or the application router should be aware of, which breaks the promise of isolation to some extent.

Application Composition is not a design pattern

You shouldn't design or plan your service to be in different applications. Sip Servlets application composition is not a chain of responsibility implementation. Applications are deployment units, not architectural building blocks. You don't design your Web Services specifically to be orchestrated with BPEL and certainly not with a particular BPEL implementation. Just like application routing, BPEL's power is in the integration.

Application composition is probably best fit for unrelated services from different vendors that consume different databases and in most cases run without being chained together with other applications. Application routing is OK as long as the actual application chaining is rare and applications are self-sufficient as individual services that have a single Web access point.

In that sense, application routing can also be used as a cheap rolling upgrade technique - just switch the requests to a new application and the new sessions will arrive in your new app while the old sessions will continue to go to the old app until they finish. AFAIK this will work on any container.

In conclusion, if you are an architect or developer you should forget that application routing exists. It is sysadmin job to reconfigure applications if they see fit or have no other choice.


Gregory Bond said...

hi vladimir

an interesting analysis but i don't completely agree with your conclusions - see my response to your article here:


Vladimir Ralev said...

Hi Gregory,

First of all, I have great respect for your team and your work. My article is most certainly not an attack against your team, your work or application routing as a whole. I very much advise the readers here to read your response and especially the "Guidelines for Modularization" section. I actually agree that other than sysadmin tool application routing can be a good thing at least in a few other areas that I don't talk about.

My conclusion is probably assumes certain interpretation for the architect and sysadmin roles. Thanks for your feedback on this topic.

Otherwise we don't disagree that much. Here are a few points that I feel are not completely explored in your response
1. I would use bash scripting (compose apps) vs Java(actual apps) analogy instead of FORTRAN(apps) vs ASM(again apps). It's not that much about the container implementations. Bash has limited power to optimize the pre-built apps to run faster together, consume less resources or share resources. This is somewhat more accurate description of the relationship between the app router/container and the applications.
2. I wouldn't be completely satisfied with having a single Web UI for N SIP apps tied to the application router configuration and aware of all schemas. If this is possible without significant maintenance effort, then it could be possible that a single SIP app can be responsible for the function of the SIP apps. This is a tough problem to mange correctly throughout the lifecycle of the service. Intra-app module dependencies are better than inter-app dependencies.
3. Without going into the semantics of design patterns, personally, my point of view is that application routing is primarily a sysadmin job. Unix pipe composition is again mostly a sysadmin tool (and bash scripting too). Application architects and developers would rarely split an application without a very good reason. (not talking about named pipes for IPC where applications must be aware of each other, which is not composition).
4. "Based on our experience, it is safe to say that the architect or developer who takes Ralev's advice and forgets that application routing exists puts themselves in jeopardy of developing an application that will be slow to market, potentially buggy and difficult to maintain." - this is too harsh. It's like saying that all software that doesn't use "certain technology" is error-prone and bad. You make some very good points about how calls can be served in a chain of stateful logic blocks assuming specific roles and acting on behalf of different entities. This is great advice. But you don't have to assume that Application Composition is the only way to do it and anything else is bad. The same can be achieved with a more lightweight implementation. Also, JEE developers are facing many of the same problems when they implement workflows involving parties in different roles. Seam, Spring, OSGI - all have some sort of dynamic reconfiguration of objects in different topologies, other helpers and data scoping to achieve injecting stages in a pipeline inside the application while sharing data and being "softly" independent. There is always embeddable BPM for complex workflows, CEP and rules engines for event stream analysis and filtering and AOP and many other reusable things from the JEE community. It is not that hard to think of alternative implementation inside a single application that focuses better on the project requirements within the scope of the project.

Eric Cheung said...

Hi Vladimir

Thanks for your blog posting and response. Great discussion.

We certainly do not believe that 'Application Composition is the only way to do it and anything else is bad'. In fact, in one of our deployments we use many forms of modularity, including call control FSM fragments, Java object libraries, and of course application routing to achieve what we consider a good balance between manageable complexity and performance (see paper for more information). It is also based on such experience that we disagree with your advise to 'forget that application routing exists'. In my opinion, application architects and developers should keep pipe-and-filter composition and application routing in their arsenal and know when to use them.

I am interested in what you think of Greg's point about high performance container as a differentiator. Clearly with the inter-application SIP dialogs and sessions within a single container there are lots of opportunities for optimization. It would benefit both application developers using application routing as a modularity mechanism, and deployers integrating applications from different vendors.

Vladimir Ralev said...

I do believe that Application Routing is great for integration of applications from different vendors. This is one of the good examples.

If I understand correctly the term "Java object library", I should highlight that intra-container Application Routing is pretty much just a Java object library. We don't do any non-Java magic in the containers. One of the great powers of Java that it supports all kinds of dynamic loading, different classloaders, classloader-based security and many isolation features that most Java developers are familiar with.

Anything that is possible to achieve with Application Routing can be achieved with a Java object library within the same application - you can implement app router, you can create new sessions for each module, implement the 289 interfaces, you can make modules exchange messages through SIP transactions, you can separate the classloaders. It's doable. It's just not exactly the best approach, in my opinion. My point is that when you have a single project within the same organization, you don't need the maximum isolation and there are plenty of Java frameworks to help you keep the reusability and CoR aspects of your modules. Even between organizations it is not so difficult to share modules.

Performance is of course the main reason I don't recommend AR. Containers can only optimize around the applications - the message passing and the behind-the-scenes objects that the applications never see (dialogs and transactions). If the developers are careful and aware of the other apps in the service they could avoid a great deal of memory duplication and database queries. But even then there is a significant penalty that in my opinion justifies the use of a more lightweight approach. The penalty also increases with the number of chained apps.

This can be indeed a very interesting discussion. I know you are invested in the Application Routing approach and you also have the performance concerns in mind. I have seen a few ideas to loosen up the Application Routing in the containers to allow higher performance and less isolation - one example is we could pass the same sessions (or dedicated shared sessions) to all applications without any transaction involved in the message passing. This would be breaking the spec, but it solves some problems. You could benefit from it as well.

Sometimes users are more interested in the deployment aspect of application routing than anything else and just seek ways to ship the service in different configurations without concerns about inter-app isolation.

Sometimes users just need app router to select application without any chaining.

There are a number of requirements I have seen from architects, but they are always a small subset of Application Routing.

Mobicents team in general is also doing a lot of research on performance and reusability topics. If there is sufficient demand for loosened Application Routing we could address this need.