TU Tran

Technologies should serve for business purpose.

NAVIGATION - SEARCH

[TinyERP]Refactoring the architecture/ pattern (Part 2)

Overview

In previous part, we learn how to scale the architecture/ pattern for simple operation (basically  CRUD operation). In this article, we will learn how to scale architecture/ pattern for more complex operation.

Create a new order

 Scenario

Your customer visits your website and selects the list of products. Lastly, they check-out which will input their credit card and submit to  the system. Then, products will be delivered to client.

Behind the scene

After submitting the order to your system, your system will:

  • Ask for the payment from customer.
  • Verifies the number of ordered products and returns errors if fail
  • Verifies customer's credit card and returns errors if fail
  • Reserve the number of ordered products. this will make sure that we have enough products for ordered products.
  • Create new order for customer.
  • Generate multiple system reports.
  • Delivery to customer

You can see that the check-out operation is complex. If we use n-tiers for this operation, the code will be hard for maintenance and system got performance issues. As the operation impact to many other parts (product, invoice, payment, shipping, ...) and update sale report, inventory report, report for shipping, .....

Let see how will we solve this problem

 Below is an overview diagram on how it should work:

 

 As diagram above:

  1. User will send their CheckOut request to server.
  2. Server receive and redirect that request to Order Module for handling that request.
  3. Order Module will store related information to storage for future use.
  4. Order Module will fire new event for newly order created inside order module. So we can sync this data to read side. Read side will prepare data in correct format as the application need. So we can access it faster.
  5. When reading information for new order. Just get from Read side. All necessary information was available.
  6. Order Module was also fire event to Inventory Module also. As this module need to know if any change on product.
  7. Order Module was also fire event to Delivery. as the order need to be deliveried to customer address and all other module that was listening on this event.

 How to implement this in TinyERP

1. User will send their CheckOut request to server.

 Using RESTClient and send create order request (checkout) to api:

 In real case of you application, the client (caller) will conduct and send request to the api in the same way. REST was popular nowadays, so you can use any programming language to do this. For more information about REST, please read through the series of article about this at this link "REST - Overview".

 

2. Server receive and redirect that request to Order Module for handling that request.

 We need to define OrderHandler, this will receive request from client

namespace App.Order.Api
{
    [RoutePrefix("api/orders")]
    public class OrderHandler : CommandHandlerController<OrderAggregate>
    {
        [Route("")]
        [HttpPost()]
        [ResponseWrapper()]
        public void CreateOrder(CreateOrderRequest request)
        {
            this.Execute(request);
        }
    }
}

We can see that, there is not business in this controller, just forward this request to appropriated handler by:

this.Execute(request);

Please noted that, request is type of CreateOrderRequest class.

Actually, where this request was handled? look at the code in "App.Order\Command\OrderCommandHandler.cs", we have:

namespace App.Order.Command
{
    internal class OrderCommandHandler : BaseCommandHandler, IOrderCommandHandler
    {
        public void Handle(CreateOrderRequest command)
        {

            OrderAggregate order = AggregateFactory.Create<OrderAggregate>();
            order.AddCustomerDetail(command.CustomerDetail);
            order.AddOrderLineItems(command.OrderLines);
            using (IUnitOfWork uow = this.CreateUnitOfWork<OrderAggregate>())
            {
                IOrderRepository repository = IoC.Container.Resolve<IOrderRepository>(uow);
                repository.Add(order);
                uow.Commit();
                order.AddEvent(new App.Order.Event.OnOrderCreated(order.Id));
            }
            order.PublishEvents();
        }
    }
}

We did not see any relationship between CreateOrderRequest and OrderCommandHandler class, how can the system know this class will handle CreateOrderRequest?

Ok, look at declaration of OrderCommandHandler, we see that it was implemented IOrderCommandHandler interface, and declaration of that interface as below:

namespace App.Order.Command
{
    public interface IOrderCommandHandler: 
        IBaseCommandHandler<CreateOrderRequest>
    {
    }
}

Ok, we found relationship between them now, from OrderHandler (in App.Order\api) received request typr of CreateOrderRequest and passed to OrderCommandHandler (in App.Order\Command), this request will be handled specifiedly  in Handle(CreateOrderRequest request) method.

3. Order Module will store related information to storage for future use

In Handle method of OrderCommandHandler, we have:

public void Handle(CreateOrderRequest command)
{

	OrderAggregate order = AggregateFactory.Create<OrderAggregate>();
	order.AddCustomerDetail(command.CustomerDetail);
	order.AddOrderLineItems(command.OrderLines);
	using (IUnitOfWork uow = this.CreateUnitOfWork<OrderAggregate>())
	{
		IOrderRepository repository = IoC.Container.Resolve<IOrderRepository>(uow);
		repository.Add(order);
		uow.Commit();
		order.AddEvent(new App.Order.Event.OnOrderCreated(order.Id));
	}
	order.PublishEvents();
}

For simplicity, We ignore validation.

In this handle method, we:

  • Create new instance of Order
  • Set customer information to order
  • Add order line item into order object
  • Save to storage

 

4. Order Module will fire new event for newly order created inside order module

look back the code in above section, we add new OnOrderCreated event into Order object:

order.AddEvent(new App.Order.Event.OnOrderCreated(order.Id));

Then, we publish those event to all subscribers:

order.PublishEvents();

How can subscribe an event in TinyERP?

There are 2 type of subscribers:

  • Internal subscriber: it means that the subscribers was located in the same application with publisher from the view of deployment. it means they are under the same application in IIS
  • External subscriber: Subscriber can any rest client that want to be notified about this event. this type of subscriber usually was deployed in another server, in different IIS application or in other country. This type of subscriber play important role in enterprise application for synchronizing data between module

For now, OnOrderCreated event will be fired, let define internal subscriber for this event.

Add new OrderEventHandler class in "App.Order\Event":

namespace App.Order.Event
{
    internal class OrderEventHandler : IOrderEventHandler
    {
        public void Execute(OnOrderCreated ev)
        {
            using (IUnitOfWork uow = new UnitOfWork(RepositoryType.MongoDb))
            {
                IOrderQuery query = IoC.Container.Resolve<IOrderQuery>(uow);
                query.Add(new App.Order.Query.Entity.Order(ev.OrderId));
                uow.Commit();
            }
        }
    }
}

In OrderEventHandler, we only receive OnOrderCreated event and create appropriated order object for query side. So the next time, we can get information of this order.

Similar to OrderCommandHandler, OrderEventHandler was implemented IOrderEventHandler interface, which inherited IEventHandler<OnOrderCreated> in-turn:

namespace App.Order.Event
{
    public interface IOrderEventHandler: 
        IEventHandler<OnOrderCreated>
    {
    }
}

5. When reading information for new order. Just get from Read side. All necessary information was available.

Just review again an overview about relationship between creating new order and retrieving detail information of that order:

 

Creating new order flow was in red and retrieving information of order was in black.

We see that, when creating new order, we store information into write db (usually in relational database for validation in the future) and also store data into read db also (in denormalized format or flat). So retrieving data, just simple read appropriated data from read db, without joining multiple tables as we usually see in MSSQL. This will improve performance of the application.

Now, let try to see how we can get data in TinyERP. append GetOrder method as below into OrderHanler in previous part:

[HttpGet()]
[Route("{orderId}")]
[ResponseWrapper()]
public OrderSummary GetOrder(string id)
{
	IOrderQuery query = IoC.Container.Resolve<IOrderQuery>();
	return query.GetOrder<OrderSummary>(id);
}

The reason why we need HttpGet or Route, ..... Please read more about this in series articles about REST at this link (REST - Overiew).

In GetOrder, we receive id as identify of order that will be returned to caller and call appropriated method of OrderQuery class.

Detail of method as below:

namespace App.Order.Query
{
    internal class OrderQuery : BaseQueryRepository<App.Order.Query.Entity.Order>, IOrderQuery
    {
        public TEntity GetOrder<TEntity>(string id) where TEntity : IMappedFrom<App.Order.Query.Entity.Order>
        {
            return this.GetById<TEntity>(id);
        }
    }
}

Woa, it was so simple. Detail information of order may be complex: list of order line, total price, promotion applied for this order, customer information. Where are they come from?

All of above information will be stored as a single record in "read db", that is why we just read appropriated record in this case.

So, does this mean that, If I need another form of order (difference information for reporting, for example). I need to create new table in read db for this form of order, is that correct?

yes, you are right, in that case we will create new schema (or table) in read db for that case, We also need to define new handler for OnOrderCreated and update appropriated data for this schema.

So create new order may take long, as it does  alot of work?

no problem, as behavior of user, they can wait long in case of creating new data, but data should be available for them in case of viewing.

 

 

For more information about other articles in this series

Thank you for reading,

Note: Please like and share to your friends if you think this is useful article, I really appreciate

 

 

 

 

Add comment