In this article I want to discuss a web services architecture that I have worked with over the last 7 years in a couple of different companies. The architecture I am discussing composes web services into layers, but there is much more to it than that. Nothing in this article is rocket science to an experienced services developer, but it is a way of working that has proven very reliable and easy to understand for developers that gets them working in a consistent way.
The diagram above shows what you will typically be used to working with in an enterprise. It is a standard 3 tiered architecture comprising of the following layers:
- Presentation Layer: This contains any views that your users will interact with like, web sites, smart phone applications, desktop clients, etc.
- Business Logic Layer: This contains the under lying logic for your application which may be comprised of web services, REST Services, windows services etc. The presentation layer shouldn’t contain any business logic and therefore should be quite dumb to the inner workings of your system, this is what the Business Logic Layer is for.
- Data Layer: This contains your data storage. This typically is an SQL database (SQL Server, Oracle, and MySQL) or one of the newer blend of NoSql Databases (RavenDB, MongoDB, Cassandra). Typically your data layer may expose stored procedures to the Business Logic Layer, or your business logic may connect via an Object relational mapper such as Enterprise Library, NHibernate or SubSonic.
This article is going to concern itself predominantly with the Web Services part of the Business Logic Layer.
What is Service Oriented Architecture
A Web service is a method of communication between two electronic devices over World Wide Web. Web service is a software function provided at a network address over the web or the cloud; it is a service that is “always on” as in the concept of utility computing.
The W3C defines a “Web service” as:
“[...] a software system designed to support interoperable machine-to-machine interaction over a network. It has an interface described in a machine-processable format (specifically WSDL). Other systems interact with the Web service in a manner prescribed by its description using SOAP messages, typically conveyed using HTTP with an XML serialization in conjunction with other Web-related standards.”
We can identify two major classes of Web services:
- REST-compliant Web services, in which the primary purpose of the service is to manipulate XML representations of Web resources using a uniform set of “stateless” operations; and
- Arbitrary Web services, in which the service may expose an arbitrary set of operations.
When building web services you can either follow service design principles that models your business domain closely, or create a more general API (Application Programmer Interface). What we are discussing here follows more of the SOA design principles. Below is a definition of a Service Oriented Architecture.
“In software engineering, a Service-Oriented Architecture (SOA) is a set of principles and methodologies for designing and developing software in the form of interoperable services. These services are well-defined business functionalities that are built as software components (discrete pieces of code and/or data structures) that can be reused for different purposes. SOA design principles are used during the phases of systems development and integration.”
To support this definition there are a series of principles \ tenets of service oriented architecture that you can follow. These are:
- Services are reusable: Regardless of whether immediate reuse opportunities exist, services are designed to support potential reuse.
- Services share a formal contract: For services to interact, they need not share any-thing but a formal contract that describes each service and design the terms of information exchange.
- Services are loosely coupled: Services must be designed to interact without the need for tight, cross-service dependencies.
- Services abstract underlying logic: The only part of a service that is visible to the outside world is what is exposed via the service contract. Underlying logic, beyond what is expressed in the descriptions that comprise the contract, is invisible and irrelevant to service requesters.
- Services are composable: Services may compose other services. This allows logic to be represented at different levels of granularity and promotes re-usability and the creation of abstraction layers.
- Services are autonomous: The logic governed by a service resides within an explicit boundary. The service has control within this boundary and is not dependent on other services for it to execute its governance.
- Services are stateless: Services should not be required to manage state information, as that can impede their ability to remain loosely coupled. Services should be designed to maximise statelessness even if that means deferring state management elsewhere, i.e. the database.
- Services are discoverable: Services should allow their descriptions to be discovered and understood by humans and service requesters that may be able to make use of their logic.
Cross Service Dependencies
A common problem I have seen before is that of cross service dependencies. This is where you have services calling other services in your organisation. Generally this is not a bad thing, but if not handled well you can end up with web service spaghetti. We had exactly this problem at my current organisation. We had services calling services, which then call other services, and in one case called back into the original service, which creates a danger of a circular reference.
When it comes to deploying changes to any of the other services you have to be mindful of the dependencies as taking a service down for even a minute whilst doing a deployment can cause outages in the rest of your system. For any company this is a bad thing as it may cause data inconsistencies, outages and general chaos during business hours. This is why we mainly had to do our service releases out of business hours, late at night. As your service estate grows, the problem can get more complicated. The way we tackled this was through a layered services design.
Layered Service Architecture
For the example in this article I will base it around the domain that I currently work in, which is financial services, the layering and services choices will obviously be different depending on the business that you work in.
As you can see above, the services are split into layers. The ordering of these layers is important. The layers are:
- Business Orchestrations: This layer is used for orchestrating calls to multiple services, more on this in a bit.
- Customer Services: This layer contains services related to our customers, i.e. Names, Addresses Phone numbers etc.
- Product Services: This layer contains services related to our different products like Loans, Cards, and Foreign Exchange etc. The product services have no notion about customer details.
- Correspondence Services: This layer is all about communication services, i.e. Email, Printing, SMS.
- Third Party Integration: This layer contains services that integrate with 3rd party systems. As an example we call out to an external Credit Scoring Agency.
- Security Services: The layer at the bottom of the stack contains services directly related to security, like user rights and roles management, authentication etc.
Running across all the services as a cross cutting concern is a services framework. In our case this framework is very light indeed; all it contains is some logging code and a few interfaces used in the hook up of the services. Apart from this very light weight framework, the services are all autonomous from each other.
It is quite common that you will need services to call other services. This is where some of the dependency headaches came from in the first place. The direction in which you can call services in this case is important. Any service in a layer can call a service in a layer below it, but not the other way around. If you need to call in the opposite direction, then you create a service in the business orchestration layer. The orchestrations in the top layer can either be standard web services themselves, or implemented as Windows Workflow Services.
Deployments and Infrastructure
When it comes to packaging and deploying these web services we package them all together into one deployment bundle. By this I mean we treat all the layers in the stack as one deployable unit. Whenever I have explained this to people in presentations, this is the one bit that has caused raised eyebrows, but it requires a little mental shift to appreciate. When you come to deploying, you don’t just think of it as deploying the Customer Service or deploying the Email service. You should think of it as releasing Version 2.6 of the Dollar Financial UK Retail Services (This is the company I work for at the time of writing this article).
So this means that on your build, all the services are packaged together into 1 MSI bundle and everything is deployed at the same time. This really makes it easier to manage dependencies.
This model can bring with it its own set of complications. You need to ensure you manage your branches correctly in source control so that you can create a release branch near release time and ensure what’s in there is stable and working. This is where unit testing, integration testing and acceptance testing become really important. If you are only releasing changes to 1 service, you still need to be able to do regression testing against everything. This would be very hard to do if you have not invested the time in putting together a comprehensive suite of integration tests. If you don’t have this in place, then packaging this way, may not be for you.
There is a lot more to this deployment mechanism that I haven’t covered yet. I will save that for another article, which will dig a little deeper into the technicalities of config file and environment management.
Now that I have covered the basic high level architectural structure of the Services, I want to discuss the physical architecture used for deployments. Our organisation uses virtual machines for our environments, specifically VMware. The infrastructure for the services is structured as a group of App Servers all running IIS 7. Each App Server has an identical version of the Services Stack installed onto it.
In the example above there are 4 App Servers. These App Servers are put into a load balancer and any client that wants to talk to the services has to go through the load balancer. It is important to have an even amount of App Servers to facilitate a Zero Downtime Deployment model.
Using an architecture like this allows you to scale out your services estate as your business grows. My current company has a retail network of nearly 600 physical bricks and mortar stores (at the time of writing). 4 App Servers in the load balancer provides enough distributed grunt to cope with the amount of services calls from our stores. As we add new stores to the retail network we can easily scale out the services by adding extra boxes into the load balancer.
Zero Downtime Deployments
Earlier in this article I mentioned that with our previous services we had to deploy late at night because we couldn’t risk any of the services being down during office hours. This really is inconvenient, because that meant I had to have a services engineer in the office late at night with a member of our technical services team to assist with the deployment. This really is no fun for anyone involved, especially if something went wrong as that engineer was in the office by himself without the support of the rest of the team. From time to time this may involve people being woken up to assist. By using the infrastructure model described above, this allowed us to practice what is known as Zero Downtime Deployment. This means we can safely deploy the services during business hours without risking downtime to the business. Here’s how it works.
First of all you take 2 servers out of the load balancer. This means you are running at half capacity in production. I always feel it is better to have more capacity than you actually need. In actual fact, our retail network can run comfortable on just 2 App Servers, so this doesn’t pose a problem. The 2 App Servers that have been removed from the loan balancer have the latest version of the services deployed onto them.
You then add these 2 App Servers back into the loan balancer, and you take the other 2 out of the load balancer. This means you are now running on the new services. You then run in production with the new services until you are satisfied everything is fine. This might be a few hours or a few days, that is up to you.
If you encounter a problem and you need to roll back, you just remove the 2 App Servers with the new services on it from the load balancer and put the other 2 back. This allows an almost immediate roll-back, and gives you time to investigate the issue properly on the other App Servers.
If after your deployment you are satisfied that everything is running well, you deploy the new services on the remaining 2 App Servers and add them back into the load balancer so you are running at full capacity again.
I have worked with this deployment model at 2 companies now and can attest to it being very reliable. If I was to move onto another company, then I would most likely architect services back into this model and have the same delivery mechanism if it isn’t already in place.
Earlier in this article I mentioned that you need to have certain testing processes in place to ensure a deployment/packaging model like this will work. I just want to spend a few moments talking about wider governance of this model and what we did to ensure everyone was playing to the same set of rules.
We put in place a governance committee made up of Developers and QA Testers. Every 2 weeks all the developers that were involved in developing services would get together to talk about what they were working on. This was also an opportunity to carry out collective code reviews so everyone had a good grasp of what was being developed.
There were different standards that the developers had to adhere too. These were:
- Coding Standards: I don’t believe in coding standards documents. They are a waste of paper because over time no one follows them. I prefer to use code productivity tools like Resharper, or Code Rush to enforce the coding standards.
- Coding Patterns: The structure of the individual services themselves were pretty much set in stone. Each service itself was composed into layers, and these layers had their own interfaces that required transposing. The way we validated service request structures was also set in stone. The governance meetings allowed us to make sure the developers were playing by the rules. I will cover these coding patterns in another article. They are not particularly complicated, but by being prescriptive it enforced consistency between each of the services in the various layers.
- Unit Test: Each service has to contain unit tests. And by this I mean proper unit test that only test specific units in isolation and do not access external resources like the file system or databases etc. I don’t believe in chasing after 100% code coverage here, but I believe 70% is a reasonable goal to aim for. It is important to ensure the critical sections of the code are covered. These tests are always executed as part of the build. If a test fails, the build fails.
- Integration / Regression Tests: As well as writing unit tests I would expect developers to write integration tests. These tests are written to test the actual service methods themselves and are allowed to access external resources. No service deployments can happen until all the integration tests are passing.
- Acceptance Tests / UI Automation Tests: We expect our QA engineers to be able to write automation tests. We use Coded UI from Microsoft. This means that we can perform tests against the service via the client applications. This is a much better real world test.
- UAT / Manual Testing: Having extensive suites of automated tests is great, but it is also still a good idea to have real people using the application. This should be a mix of QA Testers as-well as domain experts from the business.
In this article I have described at a high level an SOA architecture that I have worked with now for the past 7.5 years at 2 different companies. I am not saying this is necessarily the best way of working, or the only way working, but it is an architecture that has worked for me many times in the past. This service stack gives you a consistent and reliable way of developing and deploying services. It isn’t for everyone though; to make this work you need to ensure your build pipeline and testing procedures are quite disciplined. Without these automated tests in place it is very difficult to ensure you haven’t broken anything when you are deploying all the services as one unit.
You could of course, not go down the road of deploying all the services as one unit, but I believe this really adds to the complexity of your deployment process when you have service to service calls involved. It is up to you and your mileage may vary.
In a future article, I will dig a little deeper into some of the implementation details about how the services are structures, how we manage config files and multiple environments.