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.
Challenge
Most orders that are placed on the site will be fulfilled by our company, however there are orders that will be sent to one or more off-site fulfillment centers (not owned by us, each likely with a different API/workflow). 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:
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. If a custom implementation is not defined at the customer level, the base implementation is used:
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?)?