TU Tran

Technologies should serve for business purpose.

NAVIGATION - SEARCH

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

Overview

In this article, we will learn how to switch between the architecture/ pattern when the complexity of the application increased.

Why do we need to refactor the architecture/ pattern of our application?

Building new application based on exiting platform, frame-work is nicer. In this case, our application was belong to the platform/ framework.

Thus, we can build new features quickly and release to the market.

What will happen then, We try to build the application quickly and not care much about the quality of the application. Now we have customers, their business growing and they need to change the application for satisfying their business.

Oh my god, this is something impossible. As we did not have the full control over third-party platform/ framework. we try to cheat the code and costly to apply new changes into current platform.

It can be acceptable in small application context, but unacceptable in enterprise application where the complexity and life time of application is the most important thing we need to aware.

"YES", the BOM (board of manager) provide the budget for rebuilding the application.

This was mostly the scenario for real life. HOW CAN I AVOID THIS PROBLEM FOR MY ENTERPRISE APPLICATION USING TINYERP?

In TinyERP, we provide various patterns for different size of application as we will discuss in detail later. Please aware on how can we migrate up/ down from 1 pattern to another. You can recognize how easy we can archive the impossible mission with a few lines of code.

With simple application (mostly CRUD operation)

In this type of application, multiple layers is enough for us.

in this pattern, the whole flow was divided into multiple isolated layers. each will handle specified part of the flow.

Let see how we apply this for creating new Role object. this is purely CRUD operation.

We create mew RolesController:

namespace App.Api.Features.Security
{
    [RoutePrefix("api/roles")]
    public class RolesController : BaseApiController
    {
        [HttpPost]
        [Route("")]
        [ResponseWrapper()]
        public CreateRoleResponse CreateRole(CreateRoleRequest request)
        {
            IRoleService roleService = IoC.Container.Resolve<IRoleService>();
            CreateRoleResponse role = roleService.Create(request);
            return role;
        }
    }
}

Controller works as access-point that can be called from out-side. this will call to service layer for handling creating logic.

Data for creating new Role was contained in CreateRoleRequest dto, in service layer, We will verify the business logic of creating new Role based on this dto as below:

 

namespace App.Service.Impl.Security
{
    internal class RoleService : IRoleService
    {
        
        public CreateRoleResponse Create(CreateRoleRequest request)
        {
            this.Validate(request);
            using (App.Common.Data.IUnitOfWork uow = new App.Common.Data.UnitOfWork(RepositoryType.MSSQL))
            {
                IRoleRepository roleRepository = IoC.Container.Resolve<IRoleRepository>(uow);
                Role role = new Role(request.Name, request.Description, permissions);
                roleRepository.Add(role);
                uow.Commit();
                return ObjectHelper.Convert<CreateRoleResponse>(role);
            }
        }
        private void Validate(CreateRoleRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.Name))
            {
                throw new App.Common.Validation.ValidationException("security.addOrUpdateRole.validation.nameIsRequire");
            }
        }
    }
}

You may have a "Is RoleService class internal?" question. Please have a look at "DI & IoC" for more information. In TinyERP, we are using DI & IoC and all implementation classes will be "internal" class, this help us avoiding someone create the class directly.

In RoleService, we simply validate the passing parameter and create new instance of Role if request was validated.

RoleRepository will store Role object into database. Let see how does it work?

namespace App.Repository.Impl.Security
{
    internal class RoleRepository : BaseContentRepository<Role>, IRoleRepository
    {
        public RoleRepository() : base(new App.Context.AppDbContext(App.Common.IOMode.Read)){}
        public RoleRepository(IUnitOfWork uow) : base(uow.Context as IMSSQLDbContext){}
    }
}

Look, there is nothing in RoleRepository class, as all common behaviors (get, create, ....) was implemented in BaseContentRepository already.

In this class, we have 2 type of constructor. The parameterless will be used for reading data only. that is why we new AppDbContext with IOMode.Read as parameter.

The second constructor, with IUnitOfWork, was used for modifying data in database (can be delete, create, update an item).

To this point, You get the picture of how to use multi-layers in TinyERP.

Multiple databases in the same application

Let see, you also have product management feature, which manages the huge number of product. You want to store this information into other database named "TinyERP_Product".

This way, will keep the total size of your database small and manage multiple small databases were safer, easier than one big database.

Let see how can we archive this goal. An overview diagram about system is as below:

From diagram above, If user create Role, system will insert into system database named TinyERP as default. Otherwise, with CreateProduct command, system will insert product information into TinyERP_Product.

Now, let implement this.

The same as sample above, We also need to define new ProductsController for receiving CreateProduct command from agent.

 

namespace App.Api.Features.Product
{
    [RoutePrefix("api/products")]
    public class ProductsController : BaseApiController
    {
        [Route("")]
        public CreateProductRespone CreateProduct(CreateProductRequest request)
        {
            IProductService service = IoC.Container.Resolve<IProductService>();
            return service.CreateProduct(request);
        }
    }
}

In this controller, we also receive parameter from agent through CreateProductRequest object:

namespace App.Service.Product
{
    public class CreateProductRequest
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

We did not care how many fields the Product has, but how the execution flow. So Project only have 2 fields: Name and Price.

After product was created, we will response to agent through CreateProductResponse object:

namespace App.Service.Product
{
     public class CreateProductRespone :BaseEntity, IMappedFrom<App.Entity.SimpleProduct>
    {
    }
}

Look at ProductService class we have the logic of creating new product as below:

namespace App.Service.Impl.Product
{
    internal class ProductService : IProductService
    {
        public CreateProductRespone CreateProduct(CreateProductRequest request)
        {
            this.Validate(request);
            using (IUnitOfWork uow = UnitOfWorkFactory.Create(ConnectionStrings.Product))
            {
                IProductRepository repo = IoC.Container.Resolve<IProductRepository>(uow);
                App.Entity.SimpleProduct product = new App.Entity.SimpleProduct(request.Name, request.Price);
                repo.Add(product);
                uow.Commit();
                return ObjectHelper.Convert<CreateProductRespone>(product);
            }
        }

        private void Validate(CreateProductRequest request)
        {
            IValidationException exception = ValidationHelper.Validate(request);
            exception.ThrowIfError();
        }
    }
}

We simply validate and insert new Product object into database as creating Role object above.

So, it was the same as creating new Role, why do I write it again?

No, It was not the same. Look at using block:

using (IUnitOfWork uow = UnitOfWorkFactory.Create(ConnectionStrings.Product))
{
	/*....*/
}

When creating new instance of UnitOfWork, We will specified where data will be stored. In this case, it was specified in ConenctionStrings.Product (value is "TinyERP_Product").

Now, let open "App.Common/Configurations/configuration.debug.config", in "databases", look for element name with "TinyERP_Product", we have the following:

<?xml version="1.0" encoding="utf-8" ?>
<appconfiguration>
  <databases>
    <clear />
    <add name="TinyERP_Product" database="TinyERP_Product" server=".\SQLEXPRESS" port="1433" userName="sa" password="123456" dbType="MSSQL" default="true"></add>
    <add name="DefaultConnection" database="MyERP" server=".\SQLEXPRESS" port="1433" userName="sa" password="123456" dbType="MSSQL" default="true"></add>
  </databases>
</appconfiguration>

In this case, we will store product information into TinyERP_Product database in ".\SqlExpress" server.

So your application can break and store data of specified entity into many isolated database as needed.

Please aware that the application with many very small database is not a good.

Now, let try to run and check the result

Post create new product request:

And response from server:

Check data in database we have:

We got the huge number of request related to Product feature and want to build separated server only handle this feature. How can we do it in this case?

Normally, it will take time to config, re-build the structure of your application.

In this case, let create the new WebAPI project.

Let try to run new WebApi project, just want to make sure, it can works without any problem:

 We need to move ProductsController to new webapi project and DbContextResolver class also:

Then, update Global.asax as below:

Finally, add reference to service and repository which handling business logic:

Add the following configuration into your web.config file:

So, Your application will be deployed as below diagram:

Please aware that Product Api now is completely stand alone WebAPI project. So you can deploy to any supported web server or web-farm if you want to serve higher level of traffic.

Well done, you can build and run new App.Product.Api now.

There may be some error related to "Entityframework" reference, please add them from nuget yourselve.

Let try to build and run.

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