Thursday, 6 October 2011

SOA – In Search of Loose Coupling.

1.1 Abstract
Design of Service Oriented Systems is complicated by determining the right loosely coupled services, which, in most cases require complete analysis of the entire software system. This white paper explains some of the practical approaches in determining loose coupling.
1.2 Introduction
Service Oriented Architecture (SOA) deals with loosely coupled services. These loosely coupled services are the building blocks for the whole software. But, in most cases, SOA architects faces the most difficult challenge to determine what really is the loose coupling for the given legacy system. For a computer science graduate, loose coupling is fairly easy to define:
A service A is loosely coupled to another dependent service B if changes in service B does not affect any changes in service A.
The definition is so simple and so plain that most people may immediately say that they now understand what really is loose coupling. But, it is far more complex in a practical sense. Before we explain the practicality of loose coupling in highly complex systems, we must introduce three more new terms:
1.Atomic Services
2.Composite Services
3.Integrated Services
By Atomic services, we mean services which have no dependency on any other services. That means, they are fully independent, predictive, and self contained services. Composite services are those services that make use of one or more atomic services during its implementation. Finally, an integrated service is a service that is a mixture of composite and atomic services. It may also make use of other integrated services. This hierarchy is shown in Figure 1. As you note that, there is no loose coupling anywhere. Because, if A changes, then the entire system changes due to propagation of the result or behavior. What is the thing that make up the loose coupling?
1.3 The meaning of loose coupling
Execution of any program in a software system (also applicable to hardware) is changes of state from one to the other (a State Machine). A program runs on a processor, and the processor has registers, ALU, Memory Management Units, Interrupts, etc. At the beginning of a program, the processor initializes all it registers, memory, ALU and other processing unit to a known state (state initialization). As the program executes, the state of the registers changes and so do all part of the system like memory, disk drive, etc. The state change depends on how the program is written and what is it expected to do. Unless some random events occur into the system (like interrupts), the program is expected to pass through the same states for the same input data. Therefore, in the atomic service A (see Figure 1, the input data fed by the composite service will return the same expected result if a known input is fed (considering the fact that A is not dependent on random events). If A is expected to return solution of a linear equation, the result will always return the same value(s) if the coefficients of the linear equation
passed by X is the same. On the other hand, if A is dependent on random events, for example, getting the realtime stock quote, A is likely to return different values for the same input. The input may be “TXN” for Texas Instruments stock, the price will vary based on date and time. The expected value is still a valid value.
A is loosely coupled from X because, from the perspective of X, it does not matter what changes are made in A, as long as the input values fed into A returns the same expected values that A was producing before and after the change. Even if A changes by any means, it is transparent to X because it is getting the same expected value from A. The changes in A may be any kind. It may be program, algorithm, hardware, firmware, vendor, network, operating system, or just about any change that is imaginable. In fact, A can be remote from X and may communicate using some intermediate protocol or messages.
A web service method does not have to return a value. If A does not return a value, it will remain loosely coupled if the expected behavior before and after the change is the same. For example, if A is a service to login a user given its username and password, the expected behavior is to check the username and password then to allow login by not throwing exception or by not allowing login by throwing exception. Talking about exception, this is another area that is to be considered during loose coupling. Services throw exceptions. A service remain loosely coupled if the exceptions thrown before and after meets the same conditions. That means if A is a login service, providing no username (could be blank string) vs providing some username may generate different exception messages. To keep loose coupling the same two conditions must also be true if A is changed.
Following above explanations, we come with a new definition of loose coupling from the perspective of SOA:
A service A is loosely coupled to another service B if changes in service A does not affect any changes in service B, as long as the changes does not affect the expected values and expected behavior exposed by A including the behavior of and responses to Exceptions
To keep a service loosely coupled, all we need to make sure is that the exposed service specifications are kept exactly the same. Which indicates that the state machine will be started with the same initial values and must also return with the same expected values before and after the change. The intermediate states may arbitrarily change.
Atomic services can maintain loose coupling as long as it does not depend on operations that causes it to differ expected values. Let us consider two services A and B. Both are exactly the same code and service, but B is connected to a Robotic Arm. Because A is not connected to a tightly coupled system like the Robotic Arm, and therefore is atomic. On the other hand, B is tightly coupled and therefore not atomic anymore. Why is B tightly coupled? A Robotic Arm is a piece of hardware which can do many different thing. There are varieties of Robotic Arm. Each manufacturer has their own protocol to move the motors of the arm. The caller of B has to know how to move the arms for the Robot connected to B. If the manufacturer of the robotic arm changes, then the caller has to change the code. Therefore, this does not meet the loose coupling condition any more because changes in B is actually causing changes in X (the caller).
Therefore, an atomic service may not be operated on devices, equipment, or even other software system so that these external systems cause changes in expected values and expected behavior even though the atomic service itself has not changed.
1.1.1 Stateless or Stateful ?
A service can be designed as stateful or stateless. What does it mean? We will explain it using an example:
Let us consider the Login example. The state begins with the username and password parameters from the service caller. If this information is not supplied, then the state can not be initialized for the service to continue. Internally, the Login service will operate only on the supplied parameters and may apply various transformations as needed and may call other functions and routines, if necessary. All routines must have some relationship to the username and the password and this relationship is to validate the login. No system implementation of Login should make any kind of assumptions. If all of these are true, then Login service is stateless(see Figure 2). Because, it does not depend on any previous state or any previous execution of the same service.
A service become stateful if the first call to the service affects the subsequent call(s) to the same service. In our login service, if the user is already logged in, then the next call to the login service should respond differently. Because the user is already logged, there is no need to relogin the user. Similarly, if there is a logout method in service, the first call to logout will log the user out but the second and subsequent calls should cause an exception. But how does the Login service know that the same instance that was called before has called again ?
1.1.2 Service Instances and State Relationship
Any service, when deployed for consumption on the network, has to be instantiated. In object oriented programming, services are instantiated by using the new operator, which is available in most
Figure 1: Software as Service Architecture Comparison
Integrated Service
Y
Integrated
Service
D
Atomic
Service
C
Atomic
Service
B
Atomic
Service
A
Atomic
Service
Q
Composite
Service
P
Composite
Service
X
Integrated
Service
Z
popular object oriented programming languages (Java, C++, C#). For services written in C have to allocate necessary amount of memory and initialize the entry point so that the service is ready to serve.
In our Login example, the Login service has to be instantiated by the caller X at the first call. Depending on the design of the system, the Login object may or may not be required to be instantiated again. If a system is designed as stateless, then all the needed state information must also be provided at instantiation time and all the latest state informations have to be retrieved when the service returns. For some purposes, this approach is practical, but in many cases, keeping a service always stateless at runtime may make the service slow. In order to maintain statelessness paradigm, but yet maintain some state once the service has already been instantiated is to save state information inside the called service which may be accessible by a unique key. The handle to this key will be provided to the caller. The caller, instead of passing all the state information at every call, will simply pass the reference key. If the key is valid, the Login service will load the state information and proceed from there. When the service returns, the state information will be saved again by the Login service, which may be identified by the same key. Based on the design, a service may be both stateful or stateless.
Once a key is created, when does the state information removed? This is where the SOA Architects have full control. Many solutions exist: the state information may be deleted as soon as (1) the user logs out, (2) a timeout occur, (3) until a 'state kill' message is received, (4) keep the state until a 'state stack' is full, etc. The reference key is tied to an instance, it should be deleted along with all the state information under this key when the instance is destroyed.
1.4 How to find Loose Coupling
In a tightly coded legacy application, how would someone find the loose coupling? As an example, if your legacy application is built upon CORBA web services with implementation in C/C++, how would you port them to SOA based on Web Services or REST? The first question to be asked is how many services are expected to be exposed by the legacy system. If the answer is only one, which will be the application itself, is probably the easiest and the fasted legacy to SOA transformation. If the application itself is a web service, in most cases a simple service layer on top of the application does the job. Considering that the application is written in C/C++, the following solutions exists:
1.Build a C/C++ web services layer on top of the application using gSOAP.
2.Build a Java web services layer which calls the C/C++ using JNI.
Figure 2: Stateless Services interaction. A stateless service supplies the state information to the called service in addition to passing the parameters. If the called service returns values, the caller also is provided with the state information when the service returns.
Caller
Service
Final State
Information
Stateless
Service
Initial State
Information
Parameters
Return Values
3.If the application is CORBA enabled, then Java web services may be built and CORBA objects may be called by simple Java to CORBA mapping available by using the org.omg.CORBA packages.
4.If the application is already hosted on the web, then Java Web Services may be used to send/receive HTTP packets to the already hosted application. Typically, a selection of the right kind of ESB may do the routing and transformations.
The problem arises when the application is required to host multiple web services (be it SOAP or REST based). In such case, it depends on the design of the application how to break down the system into the number of services. The SOA Architects have to first come out with the services that is to be exposed. This may somewhat be related to what kind of services can come out of the legacy system. Defining the services is the first step. The service design should take into account the data types, data format, parameter arguments and types, return values, messaging formats, etc. If the data formats are different than the underlying application, the required transformation has to be provided before calling the application component.
After the service is designed, it is now the job to determine how to analyze the application itself such that the web services map to the application components. If the application itself is tightly coupled, then the following steps are required:
The application has to be broken down into as many sub applications as there are web services. For example, if there are 10 web services, then the application has to be broken down into 10 such sub applications. Each sub application must be independent and may be passed all the parameter and state information externally. For example, if a legacy system happen to be a billing system (see Figure 3), then it may be broken into three web services: Biller, Cashier, Processor. Biller produces the invoice, Cashier gets payment against the invoice and Processor processes the payment, which could involve some database operation. If the legacy application name is Billing, then this application must be divided into three separate subapplication (may also be processes or objects) named Biller, Cashier and Processor. If the Billing application is an executable then Biller, Cashier and Processor must also be executables. The first set of input that was passed to the Billing application will now be passed to the Biller. The Biller does processing on the input and produces some intermediate result. The result may be obtained by the caller of the application or may be saved as a file or sent as a message. We will call this return value as message. The Cashier application should be designed to receive this message and process on it without knowledge of any other information. The message should contain all the information for the Cashier to execute properly. The return value from the Cashier should be another message. The return message may then be sent to the Processor such that it's processing is entirely based on the messages sent to it.
Figure 3: Billing service example. Shows the relationship between processes, atomic services and composite services
Billing Service (Composite)
Billing Application
Biller
process
Cashier
process
Processor
process
Output
Message
Output
Message
Biller
Service
(Atomic)
Cashier
Service
(Atomic)
Processor
Service
(Atomic)
SOAP
REST
SOAP
REST
SOAP
REST
As noted, this breakdown of the application makes each sub applications loosely coupled. Now each subapplication may be hosted as independent web services where the inputs are the messages returned by the previous sub application. While this example may seem straight forward, the core idea is to split the application such that it corresponds to the web services design in order to convert them into loosely coupled independent systems.
What if the state information produced by the first stage (the Biller) is so large that it will lead to a significant message overhead. As explained earlier, applications may be created stateful by using of the reference key. So, instead of returning the entire message, only the reference key may be returned so that when Cashier receives it, it can get the state from the reference by requesting it from the Biller. Reference keying speeds up the execution of SOA applications as long as the subapplications are the same process. If the subapplications do not share a process, the state may be saved in a file or database for later retrieval. It is up to the SOA Architect on how to manage the state information in their design.
1.4.1 Any system can be loosened
Any system can be designed as loosely coupled. Loose coupling is not similar to polymorphism in object oriented programming, neither it is comparable to runtime binding in some languages. Loose coupling follows the definition we quoted earlier and all other terms like polymorphism and runtime binding are programming language specific. SOA concepts are not related to programming language concepts. On the other hand, loose coupling is system and design specific. Any tightly coupled system may be componentized into loosely coupled systems if we were able to answer the following questions:
1.Does the subsystem keep state information?
1.If yes, is it possible to set and retrieve the state information by an external program?
1.If yes, then the system can be made loosely coupled by making the state management accessible externally.
2.If no, is it possible to modify the program such that its state information could be externally managed?
1.If yes, then modify the program and make it loosely coupled.
2.If no, then seek higher level of abstraction because unless the code is rewritten, this application may not be loosely coupled.
2.If no, then the system is possible loosely coupled, but keep on following this list.
2.Is the subsystem reliant on another tightly coupled service?
1.If yes, then this subsystem is not really stateless as a whole if as it depends on another tightly coupled service. In order to make this system loosely coupled, all its dependent services must also be loosely coupled. Therefore, the tightly coupled service must be converted into loosely coupled by using this list applied on that.
2.If no, this service is possible loosely coupled, but may not be atomic.
3.Is this subsystem is the last subsystem? That is, does this service relies on any other services?
1.If yes, then this is a composite or an integrated service. For any component services to be loosely coupled, all its calling dependent services should be loosely coupled and atomic. If it is an integrated service, it relies on composite and atomic services. Integrated services are loosely coupled if all its dependent services, both composite and atomic, are also also loosely coupled.
2.If no, this is an atomic loosely coupled service.
4.You have achieved loose coupling.
1.4.2 Any system can be tightened
Why do we want to tighten a loosely coupled system? For some cases, it may be necessary. Let us consider that the Biller, Cashier and Processors services as defined in the previous example reside in the same server hardware. It is more efficient to communicate any dependencies of these services within each other by using tight coupling rather than loose coupling to eliminate the messaging overhead. Tight coupling may be achieved by reference keying as explained before by having each service provide access to their state information by an external program given the reference key is correct. Reference key may be implemented in many ways, among which are shared memory, UDP sockets, named pipe, files, and other system specific implementations. A system may be designed to automatically switch between tight coupling and loose coupling based on if the service is local (use tight coupling) or remote (use loose coupling).
1.1.3 The best SOA system design
One of the best SOA systems to design is a system that is loosely coupled but can be tightly coupled by using system specific features. An ideal service is also stateless but may be made stateful by using reference key or other state management methods. An ideal SOA service should be (1) loosely coupled and stateless, (2) loosely coupled and stateful if state can be managed externally, (3) tightly coupled, if its dependent services are not affected by tight coupling as long as the behavior of the tightly coupled services is the same as the loosely coupled version. Example to the switchable loose and tight coupling is SOALIB from Soalib Incorporated, which uses loose coupling for all services called externally, but uses tight coupling when called from SOALIB to SOALIB or internally.
1.1 Conclusion
Loose coupling is one of the core concept of Service Oriented Architecture. This concept is not always understood well by many individuals and companies. To be successful with SOA depends one's understanding of loose coupling vs. tight coupling and how to convert from tight to loose and loose to tight. It is not necessary for an SOA service to always loosely coupled if the behavior of the service does not change by loose to tight transition and as long as the service is act as if it is loosely coupled even they are in fact tightly coupled.

No comments:

Post a Comment