The union of the API architecture based on a Kappa architectural layout presents some interesting challenges.
Challenge 1: Different requests, different channels
Speaking in simple terms, any API is based on the main cornerstone, the API Endpoint component. This component (a function, a nanoservice) is receiving data from the API consumers with data requests, requests for execution of jobs, telemetries, messages and meny others. To make thing more complicated our API supports several protocols (good bye HTTP-only APIs!).
So, our primary target is to segregate the types or possible requests and direct them to the right channel (Basically two, the Slow Lane for long running tasks and the Fast Lane for Low-Latency operations).
Isolation of endpoints: An interesting architectural decision is to have a specific endpoint implementation per each service in the API, supporting several protocols. All endpoints run in High Availability mode but even if everything fails only one endpoint is affected.
Challenge 2: We do not want to get developers crazy with the implementation and maintenance of all those new endpoints!
The guy above is totally right. Tens and tens of different endpoint implementations written by different guys is not a good idea. Besides, if we implement an API architecture on Kappa and then delegate the weight of the new components to the developers they will say for sure we are sending them more work.
Given all the endpoints in architecture are generic and they do not contain any business logic, it is clear to us. They are PLATFORM COMPONENTS and they have to be created, tested and managed by the Platform (yes, Software Development Life Cycle). In order to make this possible we need to work with Prototypes. What is a prototype? Basically a generic software component that makes all the work for an API endpoint depending upon the provided configuration settings that yes, your guessed, are provided by our Delivery Platform.
OVERVIEW OF THE APPROACH
The big picture of our approach is simple. We distinguish between the Slow Lane and the Fast Lane as we said above. The Slow Lane uses queues for communication and the requests to these endpoints are job requests with a specific structure. A job request is a bunch of information from the consumer and what it wants, asking to the API “please,run this job with these parameters for me and send me the result when possible.”. The jobs are considered as long running task like report generation, merge of data, big operations (aggregation, calculation, etc) with huge amounts of data and by definition any task that can commit the general performance and stability of the system.
A crowd of endpoints channeling the requests to different Lanes through queues, topics and streams to the business logic.
On the other side, the Fast Lane endpoints receive commands with some information about the consumer and asking or posting some data. These operations must count on the lowest latency as possible since the consumers is waiting for the result to do something. The API is 100% non-blocking and everything is based on asynchronous processing which helps a lot to support the robustness and performance of the whole API.
The communication mechanism across components varies from the Slow Lane (queues and topics) and the Fast Lane (streams).
- Guarantee the segregation of long running tasks (some latency is tolerable and expected) from low-latency/real-time operations.
- Keep the processing being non-blocking and asynchronous across all steps (including data validation, storage, enrichment of information, etc).
- Store events and commands with the proper time + system + channel classification.
- Correlate properly events and commands from incoming requests to resulting outputs.
- Provide the generic prototype for automatic generation of endpoints in the API connected to the specific communication channel.
- Avoid extra workload on development teams through the automation of the endpoint generation.
- Optimize the ROI for code work, quality and performance on the same component by using the generic component approach.
From the endpoint internal perspective the main sections in the endpoint are:
- Storage (commands and events are stored in MongoDB persistence as well as internal memory).
- Validation. All data structures are validated about their correct format.
- Communications. The endpoint includes producers and consumers for streams and queues.
- Notifications. All anomalies and problems are raising alerts and notified to the alert management system in the Delivery Platform.
- Dynamic configuration settings from environment variables.
- Aggregated Log server connection.
- Integration to the other components in the API (Communications, Notifications Alerts, Operational Logging, etc).
Our first operative endpoint is a microservice based on Akka Http, written in Scala and using SBT as build tool. New models will be written in other languages (e.g. Golang) in order to minimize the footprint and achieve the best performance as possible.
Akka Http has been an invaluable tool to provide the level of generalization and abstraction from settings that we have been looking for. Absolutely recommendable.
Besides, the footprint is really low for a component of this size and features compared with traditional heavy-weight Spring-based developments.
Below you can see the profile for the idle state, consuming around 20MB of Heap memory and 9 active threads.
Under stress condition (50 concurrent users, Ramp-up period = 1 sec, Loop Count = 5) the profile changes to peaks of 1.5 GB of heap memory allocated with no request lost in any operation.
You can see the project at FEXCO Git Server.
- The API Endpoints prototypes are flexible and valid for all of the expected operational multi-protocol requests.
- The API Endpoint Prototype provides the way to automatically generate new endpoints by providing the proper specific settings. The orchestration is in charge of the Delivery Platform that interact perfectly with this approach.
- We have got to not add extra work on developers and therefore they are totally focused on the business logic.