Search code examples
architecturemicroservices

Microservice relationship/dependency strategy


I'd like some feedback on couple different solutions to handling data dependencies and relations across micro services.

Consider these services: enter image description here

Briefly explained, there is a bank service and an account service. The account service holds the accounts and are always connected to a bank using a bankId.

The dilemma is how to handle and validate this relationship and bankId and the pros and cons that comes with each decision.

Option 1:

Ignore validating completely. POST/PATCH against Accounts will never validate if the given BankId is an existing ID.

Pros

  • Services don't know about each other and there are no hard dependencies between them, if one service goes down, the other doesn't stop working. (Which is a BIG one)

Cons

  • If the BankId is incorrect, accounts are "lost" and can't be accessed.
  • The reporting service and/or any reader has to account for missing or incorrect banks and present whatever data it has without crashing.

Reflection

The services are completely decoupled which will benefit performance, up time and complexity. All readers and applications need to be "reactive" and able to handle when cross service relationships are "broken".

Option 2:

Always validate using synchronous REST-call. POST/PATCH against Accounts will fail if BankId does not exist or if in anyway BankService can't respond or is broken.

Pros

  • 100% data integrity.
  • Readers don't need to handle and expect broken relationships.

Cons

  • Services are tightly dependent, you could argue that they are no longer proper micro services and might as well be a single service.
  • Performance impacted negatively
  • AccountService POST/PATCH won't work if BankService is down, GET will still work.

Reflection

Services are tightly dependent which is really bad, this is more like the "old ways" and generally I feel like it's the wrong way to do it. Merging the services in this case is even worse, if you start fixing problems by merging you'd probably keep doing it and soon end up with massive services and you've failed with the whole micro service principle. Sure, reads will still work but that's a far fetched excuse.

Option 3:

Keep a readonly copy of BankEntity in AccountService. AccountService keeps this updated via the event bus. Validate against this on POST/PATCH.

Pros

  • 100% data integrity.
  • Readers don't need to handle and expect broken relationships.
  • No measurable negative performance impact

Cons

  • Complexity increased
  • Due to the asynchronous nature of events we cannot assume that the readonly copy of Banks are 100% updated. POST/PATCH on Account in rapid succession after creating a BankEntity might fail.
  • AccountService gets more knowledge of other services, even though it's a loose dependency

Reflection

This is the most complex way, readers won't need to handle broken relationships and the performance / up time-issues are resolved, however, instead you would have to handle the fact that the readonly copy of Banks might not be updated yet and try again later. Comparing this to Option 1 means you'd still have to deal with it in some way, and since this will be more complex across the board I'd say its not the most favourable one.

End Thoughts

The general goal that would be nice to achieve is that the services do not synchronously talk to each other and that data integrity is as good as possible.

However, in a microservice architecture, I'm under the impression that relationship integrity simply might be one of those things you accept to lose going to this way.

Our decision is leaning towards Option 1, actually just ignoring it, and anytime where you need to use it, you have to expect and handle that it might not be correct. This seems like it is the most "microservices" solution, the services don't really know about each other, and the only ones that do are applications and reporting services that need to do cross-service operations.

Any of the services need to take full responsibility that they, at any given time, has all the data they need to fully function themselves. Let's say for arguments sake that AccountEntity NEEDED a location for whatever reason to be a usable and complete domain entity, you can't expect to rely on BankId, you'd have to store Location on AccountEntity and maybe if it changes, you'd get an event and you can update it.

TL;DR What are your experiences, opinions, and thoughts on this? What would you do? Which strategy would you go for?


Solution

  • First of all the option that you choose will depend on your business needs.

    1. Ignore Completly: in your Bank and Account case i would not like to use this approach as this might result in a lost account. This type of approach you could take in cases where the business flow would ensure that sonner or later it would get resolved.
    2. Validate Always: I would not use this as such as this would make service dependent. What i would do i use a variation of this:

    ** First i would allow account to be created without the validation, the initial state of the account is CREATED

    ** Now in AccountService create a ProcessManager which listen to AccountCreated Event and in async validates it with Bank Service to check whether Bank Id is valid, if yes , update the account state to VERIFIED , or if invalid bank id , update the state to INVALID BANK ID and take the appropriate action

    1. Keep Read Only Copy: This i will not also do, because first need to duplicate lot of data, second the copy can be stale. Eg: the Bank Deleted but not known to this service or Bank Just created and not known to this service, in both the cases extra checks and async validation are required.

    Now actually all approach are valid, might depends on your critical business needs.Sometimes real time validation is required even if it creates a dependency