22 April 2013

Pub/Sub Implementation III – OrderManagement 1

IPublisher<NewOrderMessage>

Last time, we outlined the common components all of our systems will use.  In Real Life, of course, these would each have more detail, and there would probably be more of them.  But, for our purposes, this will do.

Now we're going to start Implementing some functionality.  We'll start with our Order Management system, because that's the domain we're focusing on.  As such, it is also the most complex of these pseudo-systems we're going to implement.  It implements our two interfaces a total of 5 times.  Two of those are IPublisher<T> and three are ISubscriber<T>.  This post will focus on IPublisher<NewOrderMessage>.

The first IPublisher type we'll be implementing is NewOrderMessage.  This simulates the idea that the Order Management System would have a front-end (which we will not be implementing) to create an order.  That order will then be stored, and a NewOrderMessage published for anyone who wishes to know.

That means we can't just define the Order Management System, we also have to define what an Order looks like.  Again, this will be a highly simplified example.

    public class Order
    {
        public string Id { get; set; }
        public string Customer { get; set; }
        public List<OrderLine> Details { get; set; }
        public decimal Total 
            { get { return Details.Sum(d => d.LineTotal); } }
        public DateTime OrderDate { get; set; }
 
        public Order()
        {
            OrderDate = DateTime.Now;
            Details = new List<OrderLine>();
        }
 
        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            if (obj is Order)
            {
                Order ord = (Order)obj;
                return (ord.Id == this.Id);
            }
            return false;
        }
 
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

Notice, once again, we've overridden Equals.  This is because (once again) it would be at least theoretically possible to get two different instances (and therefore two different memory locations) of the same order- and we only want to fulfill any given order once.

Next, we'll look at OrderLine.  Again, highly simplified.

public class OrderLine
    {
        public string ItemName { get; set; }
        public string ItemSKU { get; set; }
        public decimal ItemPrice { get; set; }
        public int Quantity { get; set; }
        public decimal LineTotal 
          { get { return ItemPrice * Quantity; } }
    }

Now that we have that done, we can look at the how we handle NewOrderMessages.  A lot of the code is either boring, or a (needed) variation on code we've already done.  So I'll just highlight things that are interesting or not immediately intuitive.

public NewOrderMessage CreateOrderMessage()
  {
    IPublisher<NewOrderMessage> temp = this as IPublisher<NewOrderMessage>;
    return temp.CreateMessage();
  }
 
public NewOrderMessage CreateOrderMessage(string orderId, string customerId, decimal total, Dictionary<string, int> lines)
  {
    NewOrderMessage om = new NewOrderMessage();
    om.Publisher = PUBLISHER;
    om.PublishedDate = DateTime.Now;
    om.OrderId = orderId;
    om.CustomerId = customerId;
    om.OrderTotal = total;
    om.OrderItems = lines;
    return om;
  }

These first two let me get a new OrderMessage without having to write

IPublisher<NewOrderMessage> publisher = SalesSystem as IPublisher<NewOrderMessage>;
var message = publisher.CreateMessage;

all the time.  Instead, I can simply call CreateOrderMessage with my values from the Order, and get an order message.

Here is the version we use when we're actually processing, though:

public NewOrderMessage CreateOrderMessage(Order order)
{
    string orderId = order.Id;
    string customerId = order.Customer;
    decimal total = order.Total;
    Dictionary<string, int> lines = 
        new Dictionary<string, int>();
    foreach (OrderLine line in order.Details)
    {
        lines.Add(line.ItemSKU, line.Quantity);
    }
 
    pendingOrders.Add(order, "new");
    return CreateOrderMessage(orderId, customerId, total, lines);
}

That pendingOrders.Add refers to a Dictionary where I keep my orders (simply for this example).  IRL, we'd probably have a DB with the orders and their status.

And, finally, we get to processing:

public void GetNewOrders()
{
    List<Order> orders = new List<Order>();
    int count = 0;
    do
    {
        int cursor = orders.Count;
        var temp = broker.GetPagedMessages<Order>(ORDERS, cursor, PAGE_SIZE);
        count = temp.Count();
        if (count == 0) break;
        orders.AddRange(temp);
    }
    while (count > 0);
    ProcessOrders(orders);                      
}
 
public void ProcessOrders(List<Order> orders)
{
    var query = new List<NewOrderMessage>(); 
    int count = 0;
    do
    {
       int cursor = query.Count;
       var temp = broker.GetPagedMessages<NewOrderMessage>(MESSAGING, cursor, PAGE_SIZE);
       count = temp.Count();
       if (count == 0) break;
       query.AddRange(temp);
    }
    while (count > 0);
 
   foreach (var ord in orders)
   {
       if (!pendingOrders.ContainsKey(ord) 
           && !completedOrders.Contains(ord)
           && !query.Any(q => q.OrderId == ord.Id))
       {
           IPublisher<NewOrderMessage> pub = 
            this as IPublisher<NewOrderMessage>;
             pub.Publish(this.CreateOrderMessage(ord));
       }
   }
}

GetNewOrders goes and grabs any orders we've got.  Note the "Do/While" syntax- I want to fetch records at least once, and this way I keep going back until I've got them all.

Next, I process the orders.  To do that, the first thing I do is get all the OrderMessages I've already sent.  This is really an artifact of this being an example instead of Real Life.  In Real Life, my order would be updated to some status when I had published a new order message.  For the moment, this is easier than building out a "real" order message and ALSO building in the necessary checks.

So I get all NewOrderMessage documents from the Database.  Next, I take a look at my orders, and validate that they're not already in my pending bucket, that they're not already completed, and that I have otherwise already published a message for them.  Assuming that is true, I publish the Order via NewOrderMessage.

Next, we'll look at ISubscriber<BilledOrderMessage> and ISubscriber<AllocatedOrderMessage>.

No comments:

Post a Comment