vendredi 3 mars 2017

Handing Multiple Workflows in LOB Application

Background

I'm about to build a LOB application that resembles an e-commerce solution (without paying and being open to the public). It will be used to receive orders for fulfillment purposes based on fulfillment contracts with one or more companies.

The UI will be MVC, and it's a going to be a pretty simple site. Most orders that are placed on the site will be fulfilled by our company, however there are orders that will be sent to an off-site fulfillment center (not owned by us). Simply put, there will be multiple order processing workflows. There will also likely be different requirements for calculating inventory on hand on a per-customer basis.

All that being said, I'm more of a front-end UI/UX guy and I want to confirm that the way I'm planning on handling this is reasonable.

If any of the verbiage I'm using below is incorrect, please correct me. Additionally, this is my first time using asyc/await also, so don't be shy about correcting my implementation.

Setup

Note: My boss prefers architectural patters that are as simple as we can make them without sacrificing long-term software viability.

For the sake of this question, we're only going to be dealing with the order object.

My solution currently looks like this:

enter image description here

First I created an interface called IOrderRepository:

public interface IOrderRepositoy
{
    Task<Model.Order> CreateOrderAsync(Model.Order order);
    Task<IEnumerable<Model.Order>> CreateOrderAsync(IEnumerable<Model.Order> orders);
}

Then I created a partial Business.Order class which implements IOrderRepository and inherits Model.Order from entity framework:

namespace AdventureWorks.Business
{
    internal partial class Order : Model.Order, IOrderRepositoy
    {
        public virtual async Task<IEnumerable<Model.Order>> CreateOrderAsync(IEnumerable<Model.Order> orders)
        {
            var db = new AWEntities();
            db.Orders.AddRange(orders);
            await db.SaveChangesAsync();

            return orders;
        }

        public virtual async Task<Model.Order> CreateOrderAsync(Model.Order order)
        {
            var db = new AWEntities();
            db.Orders.Add(order);
            await db.SaveChangesAsync();

            return order;
        }
    }
}

Next, I created a class to house the custom logic for a specific customer called GAC_Order. If custom logic is required, I override the CreateOrderAsync method(s) found in the partial Order class. In the example below, I chose to call base.CreateOrderAsync() as well, but there will likely be cases where base.CreateOrderAsync() does not get called.

namespace AdventureWorks.Business.Repositories
{
    internal class GAC_Order : Business.Order
    {
        public async override Task<Model.Order> CreateOrderAsync(Model.Order order)
        {
            // Custom code to override or add to base funcionality.
            // Can be run before or after base code
            // Add 5 days to required time field
            order.RequiredDate = DateTime.Now.AddDays(5);

            // Run base create order functionality
            await base.CreateOrderAsync(order);

            // Run custom functionality
            return order;
        }

        public async override Task<IEnumerable<Model.Order>> CreateOrderAsync(IEnumerable<Model.Order> orders)
        {
            // Run base create order functionality
            await base.CreateOrderAsync(orders);

            // Run custom functionality
            return orders;
        }
    }
}

Finally, I created a repository factory to determine which implementation of the IOrderRepository would be returned to me:

namespace AdventureWorks.Business
{
    public static class RepositoryFactory
    {
        public static IOrderRepositoy GetRepository(Model.Customer customer, Type repoType)
        {
            if (repoType == typeof(Business.IOrderRepositoy))
            {
                if (customer.OrderRepository == null || String.IsNullOrEmpty(customer.OrderRepository))
                {
                    return new Business.Order();
                }
                else
                {
                    var assembly = Assembly.GetExecutingAssembly();
                    var type = assembly.GetTypes().First(t => t.Name == customer.OrderRepository);

                    return Activator.CreateInstance(type) as IOrderRepositoy;
                }
            }
            else
            {
                // TODO: Used if/else and type passing to prepare for additional custom logic
            }
        }
    }
}

When I need to create an order, I call it like this:

// Create Order object
Model.Order order = new Model.Order();
order.ShipPostalCode = "30189";
order.OrderDate = DateTime.Now;

// NOTE: This would come from DB in real-world
Model.Customer customer = new Model.Customer();
customer.OrderRepository = "GAC_Order";

// Get order repository
Business.IOrderRepositoy orderRepository = Business.RepositoryFactory.GetRepository(customer, typeof(Business.IOrderRepositoy));

// Create order
orderRepository.CreateOrderAsync(order);

Questions

  • This works, but is this a valid way to account allow for a default workflow, AND also allow for other workflows on a per-customer basis?
  • Am I correct in assuming that this is a simple repository pattern (i.e., am I using the correct terminology here?)?

Aucun commentaire:

Enregistrer un commentaire