I’ve recently been discussing with a number of people about creating “reusable data services” for systems. You probably have come across such ideas or implementations dozens of times e.g. a Customer service which allows you to retrieve or amend customers. This Customer Service might have methods such as: –
The problem with service methods
However, you might have also noticed the tendency for such services to fall into one of either two categories: –
- Service methods are too broad. You decide you want to get all customers but in pages of 20 at a time. The above service only offers a “Get All Customers” service method, so you have to use that and manually perform paging yourself.
- Service methods are too fine grained. You decide you want to retrieve a Customer by their name. However, all you have is the ability to get a customer by ID. The service is useless to you because it doesn’t fit within the model that you as the consumer operates.
Both of these issues tend to arise because most of the time it’s extremely difficult to know in advance what shape your service should look like in order to be useful. Perhaps the author of the service does not know how clients will end up using the service, so has to make a best guess at it. Or perhaps the service is initially designed to service a single application, after which time they decide to “open” the service up to other clients which use it in a different manner. I’ve seen both of those happen in the past – either way, what tends to happen is that either: –
- New clients stick with a service that does not fit their needs. The client reuses existing services methods that don’t match their requirements; perhaps they write facades and adapters on top of it to make it easier to consume by massaging the services into their needs. It’s not an ideal situation and is often inefficient, but if the existing service is locked down it’s often the only choice.
- New clients write their own services. Rather than use a bad fit of a service, they avoid it entirely and go directly to the data store to construct a brand new service. However, this is only possible if the client has access to the source data, or can at least request the service from the provider. You end up with application-specific services with little reuse i.e. missing the aim of the original service, and increasing cost.
- New clients add new methods to the existing service. This fixes the issues of the new client, but now adds a whole set of new problems – the service starts to lose focus and becomes a mish-mash collection of methods that serve different clients in different ways. The service becomes fragile and causes confusion to other new users who e.g. see several ways to perform the same action.
My advice is simply this: don’t bother trying to achieve reusability through service methods. Unless your service is very clearly defined, and fulfils a very specific domain, you will struggle to achieve reusability.
Instead, these days I tend to push strongly for offering composable data services to clients. These services should simply offer up entire sets of data, and allow the client to decide how best to query / filter it themselves.
I’m of course talking about things like implementations of OData e.g. WCF Data Services. In this world, you construct a domain e.g. Customers, Orders etc. and simply surface your data as a service. You don’t have methods like “GetCustomersById” etc. etc. – you simply expose Customer data. Clients can query the service by simply asking for Customers, and then composing queries such as where, skip, take etc. on top of the query – essentially a subset of IQueryable. This query is of course evaluated on the server-side so the query is still efficient.
This gives us several benefits: –
- Forces the author to concentrate of the domain (shape) of data being exposed. This should ideally change relatively infrequently.
- Clients can easily construct service facades / repositories on top of exposed data. Reuse isn’t a problem because it’s extremely easy to construct queries either directly via e.g. URI building, or if you’re in the .NET world, there’s a LINQ provider for OData. Of course, you may as the service provider elect to publish some code or assemblies that provide common queries that can be composed on top of, but it’s not essential.
There are some good examples of OData in use at the moment including Netflix, EBay, Nuget, and Stack Overflow. Think about these – large data stores that people may wish to interrogate in a number of different ways. Can you imagine the maintenance nightmare of trying to construct explicit service methods for EBay? You might hit a percentage of those queries e.g. get auction by id – but you’d not get all of them.
The main point I’m making is that services are generally driven by the clients, not the provider of data – so don’t couple yourself to a single client when thinking about reusable services. Instead, think of the lowest common denominator and let the client themselves build services of top of that in a flexible and cost-efficient manner to the consumer, without affecting you as the provider of that data.