I’ve been thinking a lot recently about how to model non-trivial Resource Oriented systems. It is interesting that each practitioner has a different style. Peter and I have discussed endlessly about our own approaches but failed to reach consensus about the best. (Ha! as if that would ever happen anyway.) However I think part of the reason is the sheer freedom that ROC offers. This of course has it’s advantages but one of the tradeoffs is that it can lead to choice overload. As we move the resource oriented approach forward to more maturity we are going to need some good books that set out the authors approach. We are not there yet.
In the meantime I want to take a slightly different perspective. If I can’t tell you exactly what to do, it is helpful to set out broad areas of things to do and not to do. I’m not talking about anti-patterns but about constraints. This is a similar approach to that used in the classic Fielding thesis to describe REST. Fielding constrains the null architecture (which basically means anything goes.) I want to further constrain the REST approach. So if you’re not already familiar with its constraints take a quick read here. If you want a primer on ROC from the perspective of it’s principles then this older post may be helpful.
All the REST constraints of client-server, stateless, cache, uniform interface and layered apply equally to ROC.
Additional constraints of ROC over REST
General purpose datatypes The REST architecture uses the network as the messaging middleware between endpoints. This results in all representations being passed as a stream of bytes with a Content-Type header to define how to interpret those bytes. In ROC a representation can be any effectively immutable data structure or object. However not all representations are equal. It is best to minimise the number of types of representation. So for example in a human resource system we might have resources that represent employee, role, location. In an object oriented system it would be typical if each of these was represented by an object with getter methods on. In ROC we could better represent these with a general purpose data type such as XML or JSON. The advantage is less code is needed. Why? Because, firstly we don’t need to write the datatype in the first place. Secondly datatypes such as these are extensible. We can add new fields to them without modifying them. Thirdly we can use standard technologies to process them. These tool chains of accessors and transreptors eliminate code further. Other examples of general purpose data types include RDF graphs, Arrays, Maps, even strings - look how far the unix toolchain got with mainly line and tab delimited text. Of course there is occasionally a reason for custom representation, for example to provide optimised queries into the contained data. However in general use should be avoid without a compelling reason.
Technology as resources If you pick any random technology from the enterprise software shelf the chances are it will have an code API to allow a developer to integrate it into a solution. If it doesn’t it might have a REST API - that is good. It is easy to integrate a REST API into a solution using the HTTP client module which can adapt ROC requests to HTTP requests. REST APIs usually use JSON or XML too - all good. If you look at the NetKernel (either SE or EE) distributions you can see a big set of modules providing integration to many technologies. All of these technologies are exposed as endpoints (accessors, transreptors or transports) which work with resources. The author of those modules has usually adapted the code API to provide it’s functionality with the common ROC interface such that it can receive requests and return general purpose representations. Of course to do this takes more code so why bother? Firstly that code only needs to be written once and once written that technology now is plug and play. We can use it with much less knowledge of the intricate details of it’s operation. I’m not sure that point is obvious. Why should the ROC interface be simpler? Well there tends to be wildly varying styles of API design, some better than others, threading issues, object models with lots of partially documented methods. A uniform interface which ideally covers a pragmatic 80% of use cases can be used with minimal knowledge and without writing code. In the case that a module doesn’t expose an obscure feature you really need, that module can be enhanced still preserving the benefits of the approach.
Composition of information flow We have touched on this point already. Once we have all of our technologies as resources and we have a set of reusable utility endpoints for basic compute atoms we can compose these together into our business logic without the need for much code. If we do want code for a particular algorithm we can write it (Then maybe move it into an endpoint which can be further composed) but often the broad strokes of the data flow can be composed. The benefit of this is that it can be easily recomposed because the coupling is much lower than when writing code to connect objects. We have a specific composition language called DPML in NetKernel with a visual front end, nCoDE, if desired. This combined with other tools such as the space explorer and visualiser lead to much greater visibility of the structure, both statically and at runtime, of your system. So if you are lumping lots of code into a single endpoint you may be doing it wrong.
Composition of architecture Taking the composition further, ROC allows you to compose architecture. Usually this is with a category of endpoint called overlays. Approaches such as Enterprise Integration Patterns provide a similar approach but are more limited, for example by only providing asynchronous uni-directional messaging. The flexibility of ROC is that all the use cases of the REST constraint layered such as load balancing, auditing, caching and access control can be layered into an architecture at arbitrary places. In addition non-functional constraints such as timeouts, throttles, including my favourite cut-to-the-chase and a new one I discovered today circuit-breaker can be added to architectures with no code an minimal reconfiguration. Even with dynamic reconfiguration based on resources if that sophistication is needed. Often this constraint is re-enforcing the fact that constraint should be layered over composition in the Construct-Compose-Constrain micro-methodology.
Trust Caching Caching in ROC has the advantage over REST that network latency and bandwidth is not an issue. This leads to some richer patterns because we don’t need to rely on fixed periodic polling to check validity. The advantage is that we can have resources that can be served as fast as static files but which when invalidated can be done so instantaneously. Composite resources accumulate dependencies from their parts and this liberates clients of a resource from any code concerned with optimising their use of resources or indeed their underlying dependencies- they can simply request what they need every time and rely on caching to provide optimal performance. This simplifies client code. If you are feeling a desire to hold state locally within an endpoint between requests don’t. Statefulness is usually bad and unnecessary.
Verbs where possible resources that are mutable should support appropriate verbs, NEW, SINK, DELETE. This shows that you have real addressable resources and not just services. So for example you should have a request SINK res:/employee/mambo rather than SOURCE active:updateEmployee+id@mambo.
I hope these constraints provide guidance. Of course even with full adherence to these constraints I still leave wide open, to use a Rodgerism, a multi-dimensional infinite set of choices. However I hope this is a slightly smaller infinity than before.