Minimize coupling when using slim events

I have a question in regards to the Putting your events on a diet article. My question assumes a web/mobile UI consuming a web api backend that uses viewmodel composition to compose read models from multiple service DBs, and also to send commands to each service. See https://github.com/ServiceComposer/ServiceComposer.AspNetCore to understand what I use.

Although the article doesn’t go into the detail of order total charging, in my opinion, the Sales service is responsible for calculating order charge totals (subtotal, fees, taxes = total). By calculated I mean Sales is responsible for ensuring calculation happens but not necessarily what calculations are done, i.e. a Shipping service might want to add a fee so it would supply a component that would implement ICalculateSalesFees (owned and published by Sales).

When it comes to the Billing service authorizing a payment WHEN an order is placed it will need to have the currency and total at hand. I see 3 options:

  1. Sales publishes OrderPlaced with OrderId, Currency and Total. Billing has an AuthorizePolicy saga that reacts to the event and continues with its authorization process.

  2. Sales publishes OrderPlaced with OrderId only. Billing reacts to the event and a handler makes use of a Billing-owned ICalculateOrderCharges which surfaces a method to obtain OrderCharges (total and currency) for a given OrderId. Sales must implement this interface and publish (via nuget say) so that Billing can consume as required for deployment. The implementation could be direct to sales db, web api, rpc, whatever, Billing doesn’t care nor should it.

  3. Sales publishes OrderPlaced with OrderId only. Sales has an IChargeCustomer that it consumes in an OrderPlacedHandler and passes OrderCharges to the interface’s ChargeCustomer method. Billing implements IChargeCustomer as a component (again published to nuget) and Sales consumes component as the implementation of its IChargeCustomer. The implementation sends a ChargeCustomer command (owned by Billing) with the OrderCharges data.

No 1. feels wrong, as per the reasons detailed in the article mentioned.

No 2. means both services are only coupled by the need for Billing to be deployed with a registered (in DI) SalesOrderChargesCalculator component.

No 3. means both services are only coupled by the need for Sales to be deployed with a registered (in DI) BillingChargeCustomerProvider component.

Are there any benefits to using no 2. over no 3, or vice-versa. Or perhaps I’m missing some other approach to the problem?

Hi Mark, it obviously depends on the requirements of the actual business you are modeling but in my experience it’s billing that own the invoice total and the currency. Using this assumption would lead to:

  1. Sales, Shipping (and potentially other business capabilities) contributes their parts of the total amount in some agreed-upon base currency. This happens in the UI so the total would be available to Billing. (“IProvideBillingLineItem” or something)

  2. Billing provides a widget to select currency and also a widget to display the total amount to be billed based on what the customer bought, how it’s being shipped, what currency and method they wish to use to pay etc. Should the customer have a positive balance due to some refund Billing would also present and deduct this from the total billed amount. Changes to the currency, method and total amount is stored using a “StoreBillingDetailsForOrder” command if anything changes.

  3. Once the order is placed Sales will publish the OrderPlacedEvent that contains the order ID which then triggers Billing to proceed with charging the customer using the selected method, currency and amount.

With this there is no problematic coupling as I can tell?

My 2 cents

Cheers,

Andreas

Thanks for your input Andreas. However, it’s the “bit that happens in the UI” that concerns me here. Are you suggesting the final total Billing uses to send to a payment gateway is calculated in the browser process and then submitted to Billing, say as part of PlaceOrder? I would have thought the totaling calculation should always happen somewhere server-side. Can you clarify here some more please?

However, it’s the “bit that happens in the UI” that concerns me here. Are you suggesting the final total Billing uses to send to a payment gateway is calculated in the browser process and then submitted to Billing

That is correct. If the browser isn’t suitable you would have to this composition on the server side but the key here is that the things contributing to the calculation aren’t coupled to each other or the billing service that needs the “total”.

Does that make sense?

Sorry to labor on about the UI bit but I can’t imagine ever trusting an insecure client like a browser (e,g, javascript code) to provide a total that would be charged to a customer. For rendering purposes no problem, but not for passing from browser to Billing for charging purposes. That should always happen in secure code, in my opinion, to prevent malicious changes to the total. Anyhow, moving on.

the things contributing to the calculation aren’t coupled to each other or the billing service that needs the “total”.

Aren’t those components coupled to Billing by its interface IProvideBillingLineItem? Minimal I know but there is still “some” coupling, i.e. Sales (and others) have a dependency on Billing’s interface?

This style of coupling is all I was suggesting by my option 2 approach correct? Except I was possibly wrongly suggesting that the implementation in Sales did the actual totaling when it perhaps should just supply line item Ids and price.

Have you seen my presentation describing how a kind of “rules engine” from IT/Ops could be used for this scenario?

  • Udi

Thanks for the presentation reference Udi. Not sure I’ve seen that specific one but I did follow your teachings on the rules engine approach from your course. I’ll watch it nonetheless.

The rules engine approach was what I had imagined for how other services would augment their needs onto the order total. My no 2. option was close but wrong from the aspect of who owned the responsibility of rules engine execution.

As Billing is the service that needs the total I should make Billing the service responsible for executing the rules engine, rather than Sales. This approach would mean the concern of how to get the total into Billing goes away. Is that a better approach?

I see this as:

  1. A need for business capabilities to contribute to an order total is identified
  2. ITOps creates some mechanism for this composition based on non-functional requirements, eg. executing a rules engine, some secured thing browser side, etc
  3. All business capabilities that wants to contribute to this will plug in as a source
  4. All business capabilities that want to know the results plugs in as a “target”, in this case Billing

That way the business capabilities are not coupled to each other but rather to the ITOps abstraction and can evolve freely.

I guess I was missing the concept that the engine and abstraction was a part of ITOps. and so avoids business services directly coupling with each other. Thanks for pointing that out Andreas, your insights are much appreciated.