One option to integrate an event sourced (or other) microservice with existing legacy systems looks like this:
Basically, the legacy is made to “talk about what happened”. For it to do that, we instrument it so it publishes events. For example, the handler for a “Renew Magazine Subscription” button in the legacy could make a call to an event publisher after all validations and database updates have completed successfully: “Subscription Renewed” is published, making it available to new services. Over time, this can give the business the option of “freezing” the legacy; There is less and less need to make (expensive, hard to estimate, side effect prone, time consuming) changes to it because new functionality can be built by simply making new microservices subscribe to the old events. For example, a “Subscription Reminder” service might subscribe to “Subscription Started” and “Subscription Renewed”, etc. events so it knows when to send reminder emails. The service would be very idiot-savant, very simple: It does only one thing.
The Crucial Part
The crucial part of designing for such a strategy is to get the system boundaries right because we’ll get bogged down in coupling and cohesion issues otherwise. We like to establish the boundaries by applying Domain Driven Design principles:
Bounded Contexts are contexts within which a particular ubiquitous language applies. For example, if a service handles user authentication and authorization, a “user” in this context might log in, request a password reset and have a login name and password. A user in another bounded context might have the same id, but have very different behaviour and attributes. For example, if another service does “subscriptions”, a user’s subscription might have been renewed, might be about to expire or may have lapsed. Bounded contexts are a very powerful tool for keeping systems small and simple because they ensure that somebody who tries to modify magazine subscription functionality does not need to understand user authentication and vice versa. Put in automotive terms, clean bounded contexts ensure that seat belts and fan belts do not wind up in a shared belt sub-assembly and that adjusting tire pressure does not inflate the air bags.
Context maps define how language used in different contexts is related:
Bounded Context – Zoology
A bat is a flying nocturnal mammal.
Bounded Context – Cricket
A bat is a specialised piece of equipment used by batsmen to hit the ball,
They are implemented explicitly in code so that it’s as obvious as possible what a term in one context/subsystem/service/class translates to in another context.
Anti corruption layers (“ACLs”) are code artefacts which ensure that language and concepts from one (or more) bounded contexts do not leak into another context. For example, although a “user” in a legacy system may have a username, password, email, street address and membership status, the same user may only have a username, password and email in the context of user authorization. The other fields aren’t known there because they aren’t used. Including them would needlessly complicate trying to understand what authentication does and would increase the potential for side effects. (If authentication doesn’t know about membership status there won’t be any bugs caused by it changing membership status.)
Words of Warning
The effort and elapsed time to implement anti-corruption layers is usually underestimated by a lot. This is because ACLs just put makeup on the pig: Nice shiny API and events. Same old hard to estimate, side effect prone, slow release cycle, regression test intense legacy underneath. Does the legacy system provide enough value to the new services / products to be worth it?