Context, problem, and forces

Systems often need to integrate with an external system, such as Stripe for payment processing, SendGrid for transactional emails, AWS Cognito for user authentication, or a custom system. Our cloud-native systems are composed of bounded isolated components, which provide proper bulkheads to make the components responsive, resilient, and elastic. The isolation is achieved via asynchronous inter-component communication. We aim to eliminate all synchronous inter-component communication. External systems provide an Open API for inbound and outbound communication with other systems.

Integrating with an external system is inherently risky because they are typically owned by a third party and thus not within your control. Some very popular services have simply gone out of business and left their customers scrambling for alternatives. They often change their pricing models, sometimes for the better but sometimes not. They strive to keep their APIs stable, but they are always evolving and the improvements are usually but not always backwards compatible. The data models of these systems, when they do overlap with yours, are usually different. Their security models are often different. They may state a service level agreement in terms of availability, but it is wise to hedge your bets. The inbound API for invoking commands is synchronous and the outbound service provider interface (SPI) is usually a webhook that will synchronously invoke an endpoint, which you will provide. To mitigate the risks and compensate for the differences, we need to encapsulate the use of an external system such that we can adapt to the semantic and syntactic differences, be resilient to failures in the external system, and allow for easily plugging in a different provider when a switch is needed. It is also not unusual for a system to have the requirement to support multiple providers of the same capability such that a specific provider is dynamically selected based on the details of any given business transaction.