Making Roles Explicit in E-Commerce System
This post has appeared as a result of the multiple listening to the lecture of Udi Dahanan called “Intentions and Interfaces – Making Patterns Complete” (look here, if you don’t – further reading of the article won’t be interesting for you).
The main idea is to make roles in the application explicit. When designing e-commerce system, where you can make orders, it’s better to create apparently such interfaces (roles) as IMakeOrders, IPayment, etc. It will allow to make a clear separation of the application parts responsible for the different functionality.
Also quite an interesting topic of ORM resources was presented, specifically NHibernate where the following code example was given:
public class ServiceLayer { public void MakePreferred(Id customerId) { IMakeCustomerPreferred c = ORM.Get<IMakeCustomerPreferred>(customerId); c.MakePreferred(); } }
I liked it most of all, because working with such a code it is impossible not to separate different parts of application in an obvious way .
The first attempt to implement this approach was to use inheritance schemes available in NHibernate. I could not do it due to unavailability of the interfaces table (IMakeOrders, IPayment, etc.), at the same time you can’t make input into the descriminator column, because one object may implement more than one interface. Overall, you cannot achieve such application design using only mappings.
Udi Dahanan has posted in his blog the implementation of his approach. Honestly I did not like it. Yes, probably such an implementation is somehow close to DDD or to SOA, or to the bunch of other smart words, but allocating interface for every business object seems excessive.
In addition source code of NHibernate was changed in the example that would cause problems as soon as a new version of the library is issued.
I would prefer to build this abstraction at the higher level, without making changes to the source code of Session class and so on. And this is what I came to.
Let’s look at the following model:
User realizes 2 roles, IMakeOrders and IMakeCustomerPreferred. Now we would like to have an opportunity to write the following code on the aspx page:
private void MakeOrder(int productId, int customerId, int amount) { Repository<Product> productRepository = new Repository<Product>(Session); Repository<Customer> customerRepository = new Repository<Customer>(Session); Product product = productRepository.Get(productId); IMakeOrders orderMaker = customerRepository.Get<IMakeOrders>(customerId); orderMaker.MakeOrder(product, amount); }
To do this Get method in Repository class was implemented:
public TRole Get<TRole>(int id) where TRole:IRole { return (TRole) (Object) session.Get<TEntity>(id); }
But in this case no one prevents you from straight usage of the fields and methods of Customer class, that were implemented through IMakeOrders interface; to hide them you can use the explicit interface. Then the MakeOrder method will be seen only when casting Customer type to IMakeOrders. But you can leave them as simple methods if you need it.
Now let’s turn towards the role IMakeCustomerPreferred. Let’s assume that user can become preferred only if he has made more than 5 orders. In this case we have to make repository get the buyer with all his orders. It’s done by using fetching strategy. We would like to change it independently from MakeCustomerPreferred implementation. That’s why the following interface was created:
/// <summary> /// Sets fetching strategy for specified role /// </summary> /// <typeparam name="T"><see cref="IRole"/></typeparam> public interface IFetchingStrategy<T> { void AddFetchTo(ICriteria criteria); }
And the implementation:
/// <summary> /// Loads customer with his orders /// </summary> public class MakeCustomerPreferred: IFetchingStrategy<IMakeCustomerPreferred> { public void AddFetchTo(ICriteria criteria) { criteria.SetFetchMode<Customer>(x => x.Orders, FetchMode.Join); } }
Now we have to make repository use this strategy,that can be done in a such a way (changing the Get method):
public TRole Get<TRole>(int id) where TRole:IRole { ICriteria criteria = session.CreateCriteria(typeof(TEntity)); criteria.Add<TEntity>(x => x.Id == id); IEnumerable<Type> fetchingStrategies = GetFetchingStrategies<TRole>(); foreach (Type fetchingStrategyType in fetchingStrategies) { IFetchingStrategy<TRole> fetchingStrategy = (IFetchingStrategy<TRole>)Activator.CreateInstance(fetchingStrategyType); fetchingStrategy.AddFetchTo(criteria); } TRole result = (TRole) criteria.UniqueResult(); return result; } private static IEnumerable<Type> GetFetchingStrategies<TRole>() { return from t in Assembly.GetAssembly(typeof (IFetchingStrategy<IRole>)).GetTypes() where t.GetInterfaces().Contains(typeof (IFetchingStrategy<TRole>)) select t; }
GetFetchingStrategies method gets all types, implementing IFetchingStrategy
(all strategies for the role transferred). Get method creates an instance of each strategy in turn and applies it to the query. Surely, strategies search method can be simplified if Service Locator is used. The last thing I would like to mention is files location in the project; usually I do something like this:
Later I will write about testing applications with such design.
Leave a Comment