During software development, I often have heard the term abstraction of common code or DRYing out common code. In other word, my architect or lead saying "This is business logic, it should not be intermixed with your UI code. You should move this to a business delegate so other callers can use it as well." or "This code looks like it can be re-used by other callers, it should be abstracted out to a common class or function." Highly valued and respected advice.
Abstraction of common code to place where it can be re-used time and again by different callers makes for a very strong logical argument. The "DRY" (Don't Repeat Yourself) principle.
As the developer, the follow up question is often "So where exactly should I move it to?" A method in the same class? A different class? A service?
As this point, IMHO, interrogation of the scope or breadth of the abstraction is required in order to determine where to extract the common piece of functionality to. In other words, at this point the question "Who will be the consumers of this piece of code?" is the question of most importance IMO.
If the common piece of functionality is strongly tied to the application using it and callers from any other application would NOT benefit from the abstraction, then the answer becomes as simple as having the common piece of functionality defined in a Class or Function within the application so that it may be re-used.
Now if the benefits of the common code can be reaped by callers from different applications then this piece of code needs to be distributable or shareable so that callers from different applications can readily use it.
One way that the distribution and sharing can be achieved is to place the common code in a software library. For the J2EE developer, I am talking about a jar, for a C developer, a lib file. This software library is then be made accessible to callers and the callers would not need to repeat the common code.
The above approach works well when the library will not suffer much "FLUX". Flux in the code would mean distributing the changes to all consumers. Which applications do I need to distribute the jar/library to? In addition, what if we require two versions of the code to be available? There is a coordination penalty to be suffered here. The solution might work in the number of consumers are of a very low number. In addition, this piece of code is specific to Java consumers. What if this code would be beneficial to consumers written in different languages?
Using a distributed computing approach one can centralize the solution and also make it available to consumers with different languages (via Corba, REST, SOAP) . This eliminates the problem of having to distribute the jar/code to all consumers. Multiple versions of the same piece of software can be simultaneously supported using separate end points, for example, "/orderservice/v1", "/orderservice/v2".
We are moving away from a local to a global way, losing control. What I mean is when the library was self contained and distributed to only a handful of applications, things were simple and more controlled. With exposing as a distributed service, we are opening the code up to more wider audience and thus more exposure.
In deciding that a service will be distributed, one will quite likely find themselves addressing some non-functional requirements:
Semantics (Inter operability with different Consumers):
What languages will the consumers of the distributed code be in? For example, C, C++, Java or maybe only Java! This is one of the most difficult questions to answer. One would at this time pose the question "Why not just design the distributed code to simply be semantic agnostic?". For one, the native features and structures provided are often unusable and wrappers or translations are required. Translation to provide for cross language consumption has a price that to be paid. For example, with an RMI service on can depend on a total java solution and reap the benefits of the structures and semantics therein.
Quality of Service (QOS):
How much will the service be loaded, i.e., frequency of request? What is the acceptable failure rate? Are there maintenance windows that can be defined? What is the acceptable response time? How large is the payload exchanged? Scalability, fail over are some of the major players. These are some of the QOS requirements that need to be addressed.
Security:
Unlike the library distribution where the consumers are known and potentially trusted, as the service has now become "public", security becomes a valid concern. Does a consumer require authentication? Can a consumer execute the operation (authorization)? Does the data transferred between client and server require encryption?
Transactions:
A process or code when abstracted might have been a participant in a more global transaction. Consider for example, there is initially an application that will book a hotel room, a car and flight reservation as part of one single transaction. If we abstract out the flight booking to a service in light of other applications wanting to book flights, what do we do about our original process that requires all to work or have no booking at all?
Synchronicity:
Some processes are synchronous while others do not have to be. For example, when the wife commands to vacuum the house, it had better be done immediately. If the kids ask for a new toy, it can be provided later on. When designing the code, one needs to determine if the process has to be done synchronously or not? I guess this would apply equally to the library routine present in a jar.
Volatility:
It is arguable that this requirement falls under the functional requirement umbrella. Questions such as "Will the service undergo much flux?", "Do changes need to be backward compatible?", "How can separate versions of the service co-exist?", "Transition strategies from version a to version b of the service."
Discovery:
How will the shared piece of code be discovered to be usable by consumers? Registry, fixed URIs? Again security is coexistent with this requirement.
Having distributed code involves considerable thought. It is almost impossible to provide canned answers to the above as the requirement variance is large from company to company, use case to use case.
I will express some opinions based of the above:
Prophets are a rare commodity. Even if they exist, their reliability is often questionable. I feel it is often best to adopt a direction that is sound at given moment of time and space (space accounts for effort ;-)) Trying to sound smart here ;-).
If there is going to be very limited amount of semantically equivalent consumers of a duplicated piece of functionality, it is better to lean toward localization of the same. Following the direction of a shared function or library would be preferred.
If the functionality has the prospects (put on prophetic hat here) of being used by different semantics consider a service. Note that even different semantics can be achieved without a service, for example Java Native Interface or JNI.
If the functionality will be consumed by different consumers using the same semantics and one expects the number of consumers to be large, consider a distributed environment whose semantics satisfy the majority of the consumers. Thinking RMI, EJB here for Java consumers. For example, if in a total Java shop, if RMI services can be accommodated, why think of CORBA or SOAP?
If the functionality will be consumed by different consumers using different semantics, consider a language neutral implementation of the service such as COBRA, SOAP, POX, JMS etc.
Design consumers for change and flexibility. If consumers of a service or piece of functionality are developed so that they can easily adapt to change, we have a win-win situation.
To illustrate the same, consider a client that needs information about products, product identifiers, name and description in order to function.
If the code were designed as follows:
public interface ProductDAO {
public Product getProduct(Integer id);
}
with an initial implementation as shown below:
public class ProductDAOImpl implements ProductDAO {
public Product getProduct(Integer id) {
ProductModel prod = getProduct(id);
Product prod = mapToClient(prod);
return prod;
}
}
Then if the code was abstracted to a web service, the client would only need to provide a different implementation as shown below:
public class ProductSoapDaoImpl implements ProductDAO {
public Product getProduct(Integer id) {
ProductDTO dto = makeSoapCall(id);
Product prod = mapProduct(dto);
return prod;
}
private ProductDTO makeSoapCall(Integer id) {
....
}
}
The point to note is that we have an "Agile" client, i.e, a client that has been designed to adopt to change. The Product class used is still a product as far as the Client application is concerned. How its obtained is immaterial. Deciding where a client needs to be agile is again a Prophetic task! I hope to point out with the above that if "change or re-use" is anticipated, develop for the same, regardless of which layer of the architecture you are a part of.
Because we have SOA enviroment, not every piece of re-usable code needs to be a distributed Service. Put on your best prophetic hat when thinking semantics of a service. Remember securing is enduring.
Chris Angel is just freaking awesome!