One of the most challenging slides in our entire four-day Licensed ZapThink Architect (LZA) course is the one where
we explain the benefits of document style service interactions over the contrasting remote procedure call (RPC) style. On first glance, this difference in styles between services (Web services in particular) appears to be a technical detail, but in fact, the difference underscores the fundamental architectural principles of service independence and building for change.
Building for change, furthermore, is the core theme of the LZA course overall, since business agility is the core business motivator for service-oriented architecture (SOA), and business agility means building for change and leveraging change for competitive advantage. Therefore, understanding the true implications of the document style and its relationships both to service independence as well as building for change goes to the heart of what it means to do SOA properly.
The document style and building for change
For the purposes of this ZapFlash we'll work through a specific example. In our example the architect must design a Create Employee Service that basically creates a employee record in the organization's human resources database. To illustrate the need to build this service for change, we'll delineate three use cases, where the first illustrates today's requirement, the second is the requirement three months from now, and the third is the business requirement six months down the road. Let's start with the first two:
- Use case #1 (Today's use case) -- We ask the Create Employee Service to create a employee record, and it creates an empty employee record with a unique ID and returns the ID number. We'll populate the information in the employee record with a call to a separate service.
- Use case #2 (The use case in three months) -- We send the Create Employee Service a document that contains much of the information we want to track about the employee, and it populates a new employee record with that information and returns the unique ID number for that employee.
Furthermore, each of the two use cases above meets the needs, say, of separate lines of business (LOBs), so the Create Employee Service must continue to meet the needs of each LOB, even as the new use cases come our way. Remember, for this service to be loosely coupled, changing its capabilities mustn't break existing consumers of this service.
Next, let's begin with use case #1 and take the RPC style approach to implementing this Service, where for example we decide to build an Employee Information Service with four required operations: createEmployee (), readEmployee (), updateEmployee (), and deleteEmployee (). In other words, we're not creating a Create Employee Service at all. Instead, we're actually implementing an Employee Information Service, and calling the createEmployee () operation on that service, where that operation takes no parameters and returns the unique employee ID. So far so good -- this RPC style approach offers the desired functionality behind use case #1 and furthermore offers other operations as well.
The problem arises, however, when we try to add use case #2 to the mix. In order to support the new parameters for the service, we must rework the contract for the service, and thus we must also update the consumers as well, or they will be sending an invalid request to the service. Furthermore, if we were to change which information a consumer sends to the service, we'd need to change the contract and the consumers yet again.
To maintain loosely coupled services, therefore, let's implement use case #1 following the document style instead. In this situation, we build a Create Employee Service after all, and it accepts an XML document as input, where we don't really care what the document contains. Simply receiving the document is sufficient to create the employee record; the header of that document may contain other information like security-related content, but its body need contain no parameters or other information.
Now when the time comes to implement use case #2, we simply add the newly required functionality to the service in order to accept input documents with information in their bodies. If a request comes along with customer information in it, we can update the customer record. If that information is missing, then we simply follow the implementation of use case #1. Furthermore, if we change the customer information we're accepting over time, then the service can continue to support the newly added functionality without adversely impacting any consumers or requiring any changes to its contract. In other words, building document style services is essential for loose coupling.
The subtleties of service independence
The document style, however, isn't the whole story. As with most organizations, this one's requirements continue to evolve, so three months after use case #2 hits production the organization requires use case #3:
Use case #3 (The use case in six months) -- We send the Create Employee Service a document as before, only now we look to see if that employee is full time or not. If so, we go through several steps to set them up with full time employee benefits. We don't set up any benefits for part time employees. Furthermore, the business logic for setting up employees with benefits is integral to our legacy human resources (HR) application, and that legacy app will handle creating the employee record as well. So, all we need to do to add the functionality that use case #3 requires is to slap a service interface onto the legacy HR application, right? Well, not so fast.
For the sake of argument let's say that we took the simplistic wrappering approach to implementing the Create Employee Service, and furthermore, let's assume that there's a second service, the Switch Part Time to Full Time Service, that takes an existing part time employee, discontinues their part time status, and then executes the same legacy application logic as our Create Employee Service to enroll the newly full time employee in benefits.
So far so good, but here's the rub: let's say that the business requires some change to the new employee benefits enrollment part of the Create Employee Service, but not to the Switch Part Time to Full Time Service. Now, we have a problem at the service level as well as at the underlying legacy application level, because there is no visibility at the service level into the fact that changing the application functionality underlying one service might break the other service. In other words, these services share a service dependency that we would prefer to avoid.
This problem arises from the fact that we treated as atomic some application functionality we should have treated as composite. If we had abstracted the benefits enrollment process as a composition of atomic services, rather than an atomic service in its own right, then we would still have to update the process functionality when the business requirement demanded the change, but in this case that change would take place at the service composition level. As a result, we would have sufficient visibility into the impact of the change as a matter of policy as a result of the governance framework that our well-architected SOA implementation would provide, and we wouldn't need to change any existing service contracts.
However, if the business also required a change to the resulting atomic service functionality, the architect can deal with such a change in a straightforward manner as long as those atomic services interact via the document style. In such cases changing the functionality of those atomic services doesn't impact the consumers of the overall compositions, because of the loose coupling the document style enables. To achieve this breadth of loose coupling, therefore, requires document style services, architecting for service independence, and a well-architected governance framework as well.
The ZapThink take
There is more to designing the proper services than this ZapFlash can cover, of course -- see ZapThink's recent discussion of the Service design orientation as well as our older treatments of architecting for proper granularity, RPC vs. document style interactions, and the relationship between atomicity and granularity. However, while previous discussions have frequently focused on granularity issues, the issue of service dependencies centers on the application functionality of legacy service implementations more so than the granularity of their interfaces.Even more than architecting for proper atomicity and interaction style, however, the core message of this ZapFlash is how to build services for change. More than any other design principle, building for change differentiates SOA from other architectural approaches, and forms a core part of the Business Agility View of the architecture that distinguishes the true SOA architect. The biggest challenge to building for change, of course, is that we don't know what tomorrow's requirements will bring. Building document style Services and assigning legacy Services the proper level of atomicity in the context of a governance framework are essential techniques for overcoming this basic limitation.