How Would You Design This System?

A great way to learn software engineering is to consider the problems and advantages between systems design options. Today’ll pose such a system design problem to you. Feel free to send in your thoughts to olaf@codecoach.co.nz. Tomorrow I’ll discuss your responses, and I’ll give you my answer. 

OK, let’s get into it.

Imagine you’ve been given the task to design a notification system. Your system will receive messages from another system about high-value customer orders. These messages carry information about customer order events—that a new order was created, an order was approved or declined by a manager, an order was modified, and so on. 

These messages are received, say, via Amazon Simple Queue Service (SQS).

Being the sophisticated developer that you are, you design your system with Clean Architecture principles in mind—you realise the central role business logic plays in any system design. Let’s assume you will encapsulate the notification business logic within a high-level NotifyCustomerOrderUseCase class that will orchestrate the workflow. 

Clean Architecture also adheres to the idea that Business Logic, being so central, should stay independent of delivery technologies like Amazon SQS. Therefore the NotifyCustomerOrderUseCase will ingest a generalised model of a customer order event, CustomerOrderEvent, that is not tied to the original SQS message. In this way, you will keep your options open—the business logic will be able to consume messages originating from Rabbit MQ, Azure Queue, or any other queuing technology. 

Consequentially, you’ll need a class that converts the SQS message into the generalised CustomerOrderEvent type; let’s call this module the CustomerOrderEventConverter. It has a Convert() method to produce a CustomerOrderEvent from an SQS message:

  public CustomerOrderEvent Convert(SQSMessage message)
  {
     // do conversion. 
  } 

The CustomerOrderEvent is the parameter to the NotifyCustomerOrderUseCase’s Notify method():

  public void Notify(CustomerOrderEvent orderEvent)
  {
     // ..

     var notification = GenerateNotification(orderEvent);
     Notifier.Notify(notification, recipients);

     // ..
  }

It seems as if Notify() will take in the CustomerOrderEvent, and from it

  1. Generate a notification message, and then
  2. Use a Notifier service to send the notification message.

As part of our core business logic, the system must generate a specific customer order notification message for each customer order event. So

  • Order Created Event   =>   Order Created Notification
  • Order Edited Event   =>   Order Edited Notification
  • Order Approved Event   =>   Order Approved Notification
  • Order Declined Event   =>   Order Declined Notification

, and so on. 

 

  public class CustomerOrderEvent 
  {
     public string OrderId { get; set; }
     public string CustomerId { get; set;}
     public string ChangeStatus { get; set; }
     public List<OrderLine> OrderLines { get; set; }
     public decimal TotalAmount { get; set; }
     // and so on ...
  }

Where the ChangeStatus can be “CREATED”, “APPROVED”, “DECLINED”, and so on.

The notifications messages differ significantly in message content between the different message types. (Note: They might also differ as to who receives them, but that shall not be your concern for the purposes of this exercise.)

So, you’ll will need a fan-out mechanism that constructs different notification messages for different event types. You could use a switch statement or be more sophisticated. In either case, whenever the business wants to introduce a new notification for a new type of order change event, a developer must either write new code here or change existing code. Overall, code changes are required in the ‘generate a notification for the new event’ area.

We are getting closer to the crux:

Now, given that our converter will be able to ‘sense’ the type of event a given SQS message contains (i.e. “CREATED”, “APPROVED”, “DECLINED”, etc.), it could return specific subtypes for each incoming event. For example, for an “APPROVED” event, it could return an OrderApprovedEvent. When an order is paid, it could return an OrderPaidEvent. All these could conform to a common base type or interface OrderChangedEvent.

how to design this system?

 

My question to you: Would it be overall more advantageous from a system design perspective to 

  1. Keep the mapping code in CustomerOrderEventConverter generic, i.e. map each SQS message to the same CustomerOrderEvent class, or
  2. Would it be better to have CustomerOrderEventConverter produce a specific subtype for each ChangedStatus option, i.e. OrderDeclinedEvent, OrderPaidEvent, etc.?

 

Additional information:

The converter and use case will be called in succession:

  CustomerOrderEvent orderEvent = Converter.Convert(sqsMessage);
  UseCase.Notify(orderEvent );
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply