Much has been written on the design of ideal REST APIs, from Roy Fielding’s original description of HATEOAS interfaces, to much more practical approaches mirroring APIs rolled out by large technology companies. When working alone, I have a lot of freedom in how I design and build my APIs, and I always strive to design APIs which I would love to consume, based upon a number of undocumented, but strongly-held design intuitions. Collections are plurals; sub-objects are used sparingly, and mostly for practical considerations like payload size; HTTP status codes are used appropriately for particular types of errors and responses; etc.
When I work as part of a larger team, I often find that we end up building interfaces with slight inconsistencies, even if the design of them was based upon some documented high-level design principles. These inconsistencies impact the productivity of both internal and external developers which have to use these APIs, as they have to carefully parse the documentation to develop around the ‘quirks’ of the individual APIs.
Ironing out these inconsistencies can be achieved in a couple of ways, adopting a waterfall-style development model, in which each team is required to submit their detailed design specifications to an architecture council for review and sign-off; or putting a system in place which checks new API designs for consistency, and provides real-time feedback to API designers as they sketch out the interface. Oddly enough, the approach that I am going to discuss in this blog post is not the former; instead we are going to explore the Style Guide capabilities offered by Apiary.io, which allows us to develop rules governing API styles, which are assessed in real-time during API design.
Previously in this series we have examined what is required on an Access Management side in order to support a micro-services architecture, providing services for authentication, user management, assurance, etc. In this post, we expand the scope, looking at how to enable new services to easily implement access and authorisation appropriately, as well as a discussion about how they can authenticate to each other. Ultimately the creation of a secure system involves security of all parts, not just the access management services which facilitate it, and so this post focuses upon working towards enabling that. Security is also built upon organisational culture, and while it is a little difficult to instil that through a blog post, taking steps to create a technical foundation which allows the Access Management teams to be open and collaborative instead of being the team that says ‘no’ is unlikely hinder such cultural development.
Continuing from the previous post which dealt with the core concepts around performing authentication and authorisation in a distributed environment, this post expands upon those concepts, looking at additional factors for authorisation decisions, including supplementary information, authentication challenges and risk assessment. While basic authentication and authorisation requirements can be met through the use of JWTs and OAuth, this post shifts to tackling bespoke requirements, outlining potential services which could provide capabilities above and beyond what is captured in those standards.
In the previous post in this series we examined at a high-level how responsibilities for authentication and authorisation are distributed in a micro-services architecture. In this post, the strategies and technologies that underpin the implementation of authentication and authorisation will be explored further, with the core access management services providing authentication services, which support individual services performing authorisation. This discussion is actually split across two posts, as authentication and authorisation are core parts of the access management services, and require extensive discussion, with this post focusing upon the core capabilities, and the following post focussing upon more advanced authentication and authorisation requirements.
An ongoing trend, as organisations increasingly adopt cloud-native development approaches, is from centralised services, to widely distributed services. This shift requires a rethink about how access management is delivered, in terms of aligning with this model in development practice, as well as accommodating the radical effect that this has on deployment architecture. I have previously alluded to this shift as being from ‘perimeter-based access management’ to ‘centralised access management’, and how this is able to accommodate the increasing adoption of distributed infrastructure and services.
I initially began writing a post which would explore this concept in more detail, though it quickly grew beyond what could be easily captured within a single post. As a result, in order to give the topic justice, I will break this into a number of posts, beginning, in this post, by exploring what Access Management even looks like in this sort of architecture, then going into more detail about strategies for implementing a micro-services approach to access management.
There are no shortage of acronyms in the security space, and shifting towards centralised-security, rather than perimeter-based-security, has added even more. As I have been playing with solutions around centralised identity services, such as Oracle’s Identity Cloud Service, I have found myself spending more and more time in IETF RFCs in order to understand these concepts. While there is a lot of value in the standards documents, they assume a lot of knowledge and I often found myself wishing for a slightly more approachable, high level description of the elements I was dealing with. While there is something tempting about being part of the secret ‘We read the security RFCs’ club, I resisted this, and took it upon myself to provide this higher level overview of these important concepts.
The May 2017 update for ACCS (17.2.3) brought a cool new feature to ACCS, zero-downtime updates. While previously, there was support for performing a rolling restart of an application instance, where each instance would be brought down and updated in turn, this only enabled zero-downtime updates if you had an application running two or more instances, and your application could satisfy performance requirements with one fewer node. While any production system should be able to satisfy these requirements, many of the utility systems I ran were on a single node, and I had to send out an email blast to avoid being disruptive when I wanted to push a quick code fix.
Throughout my development experience, I feel that I have had several major bursts-of-learning, due to problems which have made me re-evaluate how I approach architecting and developing a solution. I feel these ultimately make me better as a programmer, or at the very least, more versatile. I am sure some of these bouts of learning and understanding are near universal, experienced by most developers, such as understanding parallelisation, but others are somewhat more specialised, such as when I first started writing games, where having to take 60+ snapshots of a continuously evolving environment every second completely changed how I thought about performance and accuracy. Developing Cloud-Native applications (and indeed micro-service based applications, which share very similar principles) feels as though it is one of these moments in my development experience, and I feel it might be interesting to reflect upon that learning process.
I see the problem statement for Cloud-Native applications as something akin to: ‘you have no idea how many instances of your application will be running, you have no idea where they will be in relation to one another and you have no idea which one will be hit for any particular call’. That is a lot of unknowns to account for in your code, and forces you to think very carefully about how you architect and develop your applications.
Try though we might to shift everything to a glorious set of stateless services, there are still plenty of scenarios in which an application might need to store some state, commonly to track some information about a user and that user’s session. In these cases, an in-memory cache is a powerful addition to an application, and is nearly indispensable to an elastic cloud architected app which is expected to be able to scale horizontally on-demand, to allow information to be shared between application instances, avoiding the need for load balancer complexity to implement sticky-sessions. Application Container Cloud Service added the ability for applications to add a Caching Service binding in the 17.1.1 drop, and in this blog post we will explore how to leverage this capability in a Node.js application.