Service-oriented architecture (SOA) and enterprise service bus (ESB) are two buzzwords that have gained significant momentum and traction in live architectures over the last few years. Speaking from firsthand experience, I would say SOA/ESB techniques do deliver increased modularity, flexibility and reusability of components, which has been the age-old promise of object-oriented programming (OOP) and even general software engineering. In particular, SOA/ESB appears to extend effectively on the hallowed OOP principle of encapsulation. But it seems all new paradigms come with a "dark side" that must be effectively recognized and managed to minimize undesirable consequences. What is the dark side of SOA/ESB? This article examines it and proposes a powerful technique and "quasi-architecture" as the solution.
The dark side of SOA/ESB is increased runtime and testing complexity. A complex SOA/ESB architecture can resemble a circuit wiring diagram, where service calls are the wires and operations are the components. However, circuits have an inherent one-way flow of electricity, and SOA can involve two-way communication (service A calls service B, service B calls service A). If you have n services, there are n-squared possible interactions between those services. Even limiting a system to some small subset of that total can lead to a tangle. Also, often later into development, one might realize that a particular function covered by service A is better handled by service B. Depending on the design, moving (refactoring) code between service boundaries can be difficult, time consuming and even fraught with peril. Is there some way to minimize all that?
Outside this custom-built complexity is the problem of the container. For this article, "container" refers to a Java Platform Enterprise Edition (Java EE) Web server that supports Java Message Service (JMS) and, say, message-driven Enterprise JavaBeans (EJB), the basic and increasingly standardized enterprise approach to SOA/ESB. Two containers I have worked with are BEA WebLogic and JBoss. (They have properties similar to other containers, such as IBM WebSphere.) Despite all its power and justifiable fanfare, the container has a dark side too. I worked on a system involving just a half-dozen services, but noticed the following (note that many of these issues are also associated with simple client-server applications but are exacerbated with SOA/ESB):
Long startup and shutdown time for the container. At several seconds per service, startup time for all services was nearing two minutes.
Difficulty in debugging one service independently of another. If one service B is incorrectly compiling or behaving, it can be difficult for another service A to function, even if service A does not depend on B.
Complex classpath setup requirements sometimes interfere with correct functioning.
Complex container setup requirements. A distribution directory must be set up with the right jar files, Spring/Hibernate configuration files, etc. It is neither simple nor automatic to keep the distribution directory synched up with code changes.
On Windows, (re)compiles/builds can fail because the container is "holding on" to files such as log files or jar files; i.e., a delete or overwrite of a file fails because the file is open by some process. Hence, the container often must be shut down and restarted just to do a rebuild/redeploy of the code.
Using the debugger generally requires setting up a remote debugger. A developer must interrupt the process of running through
code and frequently connect/disconnect. New deployment requires disconnecting/reconnecting the debugger. Many developers never
set up remote debugging and resort to crude System.out.println and — not intentionally, but in practice — long edit-compile-test cycles.
Since the container is inherently multithreaded, the debugging view is significantly complicated. WebLogic immediately spawns dozens, or more, helper threads on startup even if none are used immediately, or ever. This requires initialization and memory, and dormant threads seem to slow JVM performance on active threads—i.e., a significant waste of resources for "low load" situations.
Too much output
Must restart the container to make log4j logging-level changes
Logs "scroll" to new files, and a simple scan does not keep up, thereby seeming to falsely indicate a code hang
Difficult to understand what the system is doing if there is no log output
Must recompile lots of code just to make minor new logging calls
Cannot access critical internal variables and stack state
All of the above interfere, sometimes in a time-consuming way, with the critical edit-compile-test loop, where developers spend most of their time. This overall cost is not obvious, difficult to quantify, and almost invisible. But at the end of the day, it can all add up substantially. Developers do not always realize how much time they spend setting up the system just to get the code to run, because it is sometimes a mechanical, almost-unconscious detour.
Can one mix SOA/ESB with RAD, or rapid application development? The above enumeration of the quite substantial "dark side" seems to preclude it. The container (and Java EE at times) could be said to suffer from "monolithicity."
Contrast the massive overhead described above to the fast approach of running code in the Eclipse environment. The developer can just right-click on a Java file and run it as an application or as a JUnit test to instantly start executing the code — just point and click, or plug and play.
Eclipse always automatically compiles the code. And it supports complex inter-package dependencies. This mode of execution is sometimes called "container-less," to contrast it with its complementary mode. The big question is: "Why is executing code in the Eclipse environment so quick and easy, as compared to executing code within the container?"
The answer is clearly that the container provides many subsystems that "wrap" the code in various layers of insulation, so to speak. The major capabilities provided are:
Concurrency: Any number of message-driven EJB components can run simultaneously on one machine.
Transactions: Atomic commit/rollback of the whole service call or parts of it.
Pooling: Such as with threads or database connections.
Distributed computing: The same EJB can be run on multiple machines.
For this article, let's call the general union of the above properties "scalability."
What is remarkable is how little of this container-centric functionality developers care about in just implementing specific business logic, generally the main point of value for custom enterprise development. Most business logic is not really oriented around any of the above areas. Thus, programmers must deal with two major layers of complexity: application logic and container wrapping.
If all containers are somewhat interchangeable, then a programmer's coding effort that frequently focuses on a container's general minutiae (say, for example, JMS message-passing infrastructure, or mock/test/simulation systems) can be repetitive and unproductive, or in other words, not valuable. Yet, on the multi-developer SOA/ESB projects with which I've worked, the developers spend significant, even excessive time on all the container wrapping.
This is part of the core of what industry analysts such as Bruce Tate (in his book Beyond Java) complain about in their criticisms of the complexity of Java EE. But if this is the case, how can we develop code that minimizes time spent on container wrapping? Is there some kind of architecture approach that can minimize the amount of time the programmer deals with container wrapping?
Archived Discussions (Read only)