Service Output Architecture
The genesis for this series is looking at how to leverage the benefits of serverless functions as a service when designing a system while keeping open the option to change as the system evolves.
In part 1 we looked as the definition of a service. In this post we'll explore the possible classes of output from a service.
Thinking about individual service based computing. and the idea of Function as a Service. I have been interested in what is the Unit of work of each service provides and what, if any, standardized output contract can we design to allow for consistent and predicable behavior across services. At root here are the different outcomes that could be possible:
Successful: Is the most basic successful response and indicates that whatever was expected to occur as the happy path, occurred.
Created: this status could be returned if a new resource was created in the course of the service operation and we need to indicate that the resource was created back to the caller.
Fulfilled by existing resource: The idea here is that the request, as provided matches precisely with an existing resource and the previous resource can be safely returned. An example, might be storing a phone number. If the request is to store a number and the previous number already exists then we can return this status code as a way to indicate that the request was successful but that no changes were made. This helps the calling code to decide what to do with this information. For example, and can provide a level of idempotency support.
Queued: The request was received but is being queued for future processing. There is an assumption of future processing but at this point there is no guarentee. The caller would need to use to another method to validate the success of the call (e.g. postback, or polling a retrieval service).
The failure states come from three potential areas:
- Business or Logic Errors - that are expected and planned for.
- System Errors - these are unexpected errors and not planned for
- Upstream Errors - these are errors that are outside of the direct control of the service.
Business or Logic Errors
Resource not found: This status is returned if the was a request for a specific resource to retrieve or modify and the resource does not exist.
Validation error: This status indicates that something was wrong with the request payload, or the intent of the call. E.g. it could be that a parameter was not correct in the input payload, or this could also indicate a business error. In either case there is an assumption that response message will provide useful information in relation to the specific issue that is causing the error. In the case of a malformed request the information provided in the response object should be sufficient to allow the user to correct the error. For a business error there may or may not be something the caller can do to recover the error but the error should exist due a problem with the request message.
Conflicting request error: In this scenario the caller is providing a request object that would conflict with an existing resource, or state of an existing resource and cannot be reconciled automatically. This could be due to a duplicate object being created, or a business logic error that is preventing the service from completing as expected.
Transient and Permanent error: In each case the error is system related not business related. If retrying the request could succeed then the transient status is returned.
Rate Limited: If the service has controls for number of requests, then the service could returned this status if that rate is exceeded.
Upstream timeout: If this service has a dependency and an upstream service had timed out then this service will not know the true state of the upstream dependency. If that scenario is not handled internally then the service will report the timeout.
Upstream error: This service may rely on other services (both internal and vendor). Upstream errors should be treated similarly to transient errors, however, the source of the error is external to the service being called.
Supporting HTTP Web APIs
As the service can be used inside of a web API we should determine what appropriate HTTP status code should be returned based on the service result. It is no coincidence that there is almost a 1-1 match with an existing HTTP status code.
Outcome) C --> E(Successful) E -->|http| E1(200 - OK
Returned) C --> F(Created) F -->|http| F1(201 - Created
Returned) C --> G(Fulfilled
Resource) G -->|http| F2(200 - OK
Returned) C --> H(Queued) H -->|http|H1(202 - Accepted) B -----> D(Failed
Outcome) D --> BB(Business/Logic Errors) BB --> L(Resource
Not Found) L -->|http| L1(404 - Not Found) BB --> I0(Validation
Error) I0 -->|http| I1(400 - Bad Request
Returned) BB --> M(Conflicting Request) M -->|http|M2(409 - Conflict) D --> SS(System Errors) SS --> I(Transient
Error) I -->|http|I2(503 - Service Unavailable) SS --> J(Permanent
Error) J -->|http|J2(500 - Server Error) SS --> N(Rate Limited) N --> |http|N1(429 - Too Many Requests) D --> DD(Dependency Errors) DD --> KK(Upstream
Timeout) KK -->|http|KK2(504 - Gateway Timeout) DD --> K(Upstream
Error) K -->|http|K2(502 - Bad Gateway)
Now that we've defined the discrete set of responses from a service in our next post we can look at how service dependencies can inter-operate.