Can REST Expose Course Grained APIs?
Firstly, I must state that I am an avid fan of REST and always try to use it where possible. I researched REST for my bachelors dissertation at University, and whilst I found it made a lot of academic sense, I always struggled to see its commercial viability for exposing complex APIs.
My doubts have since been confirmed as we've started to adopt Patterns of Enterprise Application Architecture within our designs. More specifically, we have identified that REST cannot expose coarse-grained interfaces due to its inability to mix action types and limited support for referencing multiple resources in a single operation.
Why would we want to do this? Well, let's take a look at how an enterprise application is layered using P of EAA design concepts:
- Data Access Layer (typically an ORM). Provides a core querying interface (e.g. CRUD).
- Domain Layer. Provides a set of basic operations which conform to the system's constraints (e.g. a 'CreateAccount' method performing validation, or a 'GetByPassword' method performing hashing).
- Service Layer. Provides access to the system's use cases (e.g. 'Delete members who have not paid their latest bill and update product availability').
Here we can see 3 layers with varying levels of granularity, ranging from fine to coarse, respectively. The former 2 layers encapsulate most of the system's complexity, whereas the latter is essentially a collection of façades which route calls off to the domain logic. The importance of this layer is controversial, but it does provide several significant benefits to justify its appearance in most designs:
- Coarse-grained service layers provide a high level of abstraction which map directly onto use cases, thus making the codebase very descriptive.
- Service layers can also adsorb considerable changes in requirements, since they abstract away the workflow of each use case. Additive changes to use cases often require new domain layer methods to be implemented, since they cannot sensibly be applied by mutating existing domain layer methods. For example ‘Unsubscribe from Newsletter’ and ‘Delete Member’ are orthogonal operations, so their implementations shouldn't be combined. Instead, they should be implemented distinctly in the domain layer, and combined into a workflow as a service layer method to represent the use case for un-registering members. Systems without service layers require extensive inspection to identify and refactor code blocks which represent changing use cases.
- Finally (and more relevantly), they also provide a more efficient foundation for performing RPCs, since fewer methods have to be called to achieve a given task.
Now, it is apparent that a REST API could easily expose the data access layer. In fact, a REST API could quite easily expose the domain layer too, since the interface is still relatively fine-grained at this point.
However, the service layer is very different. Not only are we interacting with 3 resources in this example, but we're also using 2 different actions (delete and update), thus making the operation impossible to express in a single HTTP request. If we did decide to split the actions into 2 operations, then we would immediately be faced with transactional complexities.
Unfortunately I cannot see an easy way to remedy this. I therefore find myself coming to the same conclusion as many others have: SOA for complex enterprise APIs, REST for simple or adhoc APIs. Apologies for selling-out on you REST, at least you can't say I didn't try.
Isolating Class Interactions
This post defines 3 horizontal tiers for decoupling code by isolating class interactions within and between packages. Horizontal tiers exist as a contrast to standard tiers (or 'vertical tiers'), which are concerned with isolating levels of service as opposed to class interactions. These horizontal tiers consist of factories, providers and logic, which respectively correlate with creation time, scope management and run time. These facets are expressed as follows:
Factories are responsible for defining the system’s concrete implementations. This is achieved by assigning factory methods to each dependable interface within the system. These methods instantiate concrete implementations and wire their dependencies accordingly, typically via constructor DI.
Providers are responsible for ensuring objects are leased in a way which best suits the system’s scope model. Providers achieve this by wrapping the underlying factories with additional logic which handles the reuse of objects. A provider will invariably call a factory to obtain a new object. However, the provider may then retain said object and return the same instance to subsequent requests, or it may pool them or use any other number of techniques.
Logic defines the implemented method bodies for each of the system’s interfaces. Whilst factories and providers must be initialised on start-up, logic should be called on demand in response to events, such as connection requests or key strokes. Simply put: the logic is the pure runtime code, i.e. what’s left of a method body once creation time and scope management concerns have been removed.
Whilst these tiers promote the use of DI, it is important to note that this design pattern is not always feasible. The first reason is obvious: we must perform at least one call directly to a provider to obtain our first object, which in turn should use DI. This initial call is technically service location and is conceptually unavoidable. The second reason is that objects initialised by third-party components cannot be injected, since they are constructed outside of the controlled environment. Service location must therefore be used as an alternative, but only if the problem cannot be reworked.
