4

Here's an abstract question with real world implications.

I have two microservices; let's call them the CreditCardsService and the SubscriptionsService.

I also have a SPA that is supposed to use the SubscriptionsService so that customers can subscribe. To do that, the SubscriptionsService has an endpoint where you can POST a subscription model to create a subscription, and in that model is a creditCardId that points to a credit card that should pay for the subscription. There are certain business rules that say whether or not you can use said credit card for the subscription (expiration is more than 12 months away, it's a VISA, etc). These specific business rules are tied to the SubscriptionsService

The problem is that the team working on the SPA want a /CreditCards endpoint in the SubscriptonsService that returns all valid credit cards of the user that can be used in the subscriptions model. They don't want to implement the same business validation rules in the SPA that are in the SubscriptionsService itself.

To me this seems to go against the SOLID principles that are central to microservice design; specifically separation of concerns. I also ask myself, what precedent is this going to set? Are we going to have to add a /CreditCards endpoint to the OrdersService or any other service that might use creditCardId as a property of it's model?

So the main question is this: What is the best way to design this? Should the business validation logic be duplicated between the frontend and the backend? Should this new endpoint be added to the SubscriptionsService? Should we try to simplify the business logic?

TallTed
  • 9,069
  • 2
  • 22
  • 37
gislikonrad
  • 3,401
  • 2
  • 22
  • 24

4 Answers4

3

It is a completely fair request and you should offer that endpoint. If you define the rules for what CC is valid for your service, then you should offer any and all help dealing with it too.

Logic should not be repeated. That tend to make systems unmaintainable.

This has less to do with SOLID, although SRP would also say, that if you are responsible for something then any related logic also belongs to you. This concern can not be separated from your service, since it is defined there.

As a solution option, I would perhaps look into whether I can get away with linking to the CC Service since you already have one. Can I redirect the client with a constructed query perhaps to the CC Service to get all relevant CCs, without actually knowing them in the Subscription Service.

Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38
  • 1
    The CreditCardSpecification idea to help decoupling both contexts is an interesting idea. While the specification could live in a shared package the CC entities themselves wouldn't have to be known by the Subscription's context. You could implement something like, `validCardSpec = subscriptionService.validCreditCardSpecificationFor(...); validCards = creditCardService.allMatching(validCardsSpec);`. I'm not sure whether this is better than a direct dependency between both contexts, but it's an idea to be explored for sure. – plalx Feb 06 '19 at 18:36
  • Although I do agree to a point that logic shouldn't be repeated, I also feel that adding this specific endpoint couples the service more together and goes against the single responsibility principle, which is central to microservices. The precedent being set is also something to consider, since this was a simplified example of my problem. We have a service environment that has over 100 services at the moment and will probably end up having several hundred services. – gislikonrad Feb 07 '19 at 13:32
2

What is the best way to design this? Should the business validation logic be duplicated between the frontend and the backend? Should this new endpoint be added to the SubscriptionsService? Should we try to simplify the business logic?

From my point of view, I would integrate "Subscription BC" (S-BC) with "CreditCards BC" (CC-BC). CC-BC is upstream and S-BC is downstream. You could do it with REST API in CC-BC, or with a message queue.

But what I validate is the operation done with a CC, not the CC itself, i.e. validate "is this CC valid for subscription". And that validation is in S-BC.

If the SPA wants to retrieve "the CCs of a user that he/she can use for subscription", it is a functionality of the S-BC.

The client (SPA) should call the the S-BC API to use that functionality, and the S-BC performs the functionality getting the CCs from the CC-BC and doing the validation.

choquero70
  • 4,470
  • 2
  • 28
  • 48
  • I'm assuming "BC" in your example is Bounded Context. So you would say that the list of valid credit cards for a subscription is part of the subscription bounded context? – gislikonrad Feb 07 '19 at 13:39
  • Yes, BC is bounded context. And re-reading my answer and taking a look to how I do validation in some project of mine I've realized that I didn't put it right in the answer. I will correct it as soon as possible. Anyway subscription BC isn't clear for me. What entities do you have there? And how is the contract of the subscribe operation? Thanks – choquero70 Feb 07 '19 at 14:54
  • Hello, after re-thinking the design, I've decided that I don't change my answer. I think I would do it that way. Yes, the list of valid credit cards for a subscription would be returned by the S-BC, which obtain them from the CC-BC (which is upstream). You integrate S-BC either calling a REST API of CC-BC, or mantaining a copy of credit cards in S-BC database, that is updated listening to events that are published by CC-BC. S-BC must know of credit cards. It gets them from CC-BC and return them to client (SPA) – choquero70 Feb 08 '19 at 19:01
  • Even more, if you had another ways for paying a subscription, you would have to integrate S-BC with them as well, and validate them in the S-BC. In the S-BC you would have the "abstraction" "way of paying", being credit cards one of them. – choquero70 Feb 08 '19 at 19:01
1

Even when the source of truth is the domain model and ultimately you must have validation at the domain model level, validation can still be handled at both the domain model level (server side) and the UI (client side). Client-side validation is a great convenience for users. It saves time they would otherwise spend waiting for a round trip to the server that might return validation errors. In business terms, even a few fractions of seconds multiplied hundreds of times each day adds up to a lot of time, expense, and frustration. Straightforward and immediate validation enables users to work more efficiently and produce better quality input and output. Just as the view model and the domain model are different, view model validation and domain model validation might be similar but serve a different purpose. If you are concerned about DRY (the Don’t Repeat Yourself principle), consider that in this case code reuse might also mean coupling, and in enterprise applications it is more important not to couple the server side to the client side than to follow the DRY principle. (NET-Microservices-Architecture-for-Containerized-NET-Applications book)

DmitriBodiu
  • 1,120
  • 1
  • 11
  • 22
  • 1
    My concern is not whether the SubscriptionService is validating this data itself. It absolutely should. My concern is whether it should have it's own /CreditCards endpoint which returns only the credit card entities that are valid for subscriptions. – gislikonrad Feb 07 '19 at 13:34
1

In microservices and DDD the subscriptions service should have a credit cards endpoint if that is data that is relevant to the bounded context of subscriptions.

The creditcards endpoint might serve a slightly different model of data than you would find in the credit cards service itself, because in the context of subscriptions a credit card might look or behave differently. The subscriptions service would have a creditcards table or backing store, probably, to support storing its own schema of creditcards and refer to some source of truth to keep that data in good shape (for example messages about card events on a bus, or some other mechanism).

This enables 3 things, firstly the subscriptions service wont be completely knocked out if cards goes down for a while, it can refer to its own table and work anyway. Secondly your domain code will be more focused as it will only have to deal with the properties of credit cards that really matter to solving the current problem. Finally if your cards store can even have extra domain specific properties that are computed and materialized on store.

Obligatory Fowler link : Bounded Context Pattern

Peter Short
  • 762
  • 6
  • 17
  • I like this answer. It's similar to the stellar answer I got from @robert-bräutigam, but it also has the obligatory fowler link to sum things up. – gislikonrad Feb 07 '19 at 15:05