By now I’ve got rid of most of the soot resulting from the reactions on a previous post. Flames were pointed at my need for a case selector. In this post I will delve a little deeper into that. I’m only presenting the core of the problem, the point where the case seems unavoidable and hoping for any more elegant solutions from you. I’m on a tightrope between an (over-)simplification of the problem, a strict NDA and you as a critical reader. Nevertheless I think the problem is worth some reflections so I am giving at a try.
The domain
The domain of our system handles the reselling of licenses. A subscription has a any number of running licenses, to which it’s not much more than a key and an expiration date. Which makes a simple model, a subscription has a list of licenses. The crooked part is (and should be) completely invisible to the subscription. It is where we buy these licenses, we have several suppliers for them. Each license supplier has a completely different way we should interact with them. For one we do the complete administration and send them an Excel sheet every three months. For another one they do all administration themselves, every detail is communicated over a web service. For yet another one there is another different scenario. The list of these suppliers will grow over time. Adding another supplier will require writing specific code, but this code should affect other parts of the system as little as possible.
Having data stored in different ways (db table, web service, spreadsheet) per entity is not that great a problem; there is a vertical split. The core of our problem is that we have the data of one entity stored in a variety of ways; we have a horizontal split.
Consequences for the implementation
Having such different ways to get to the data has the consequence that the internal behavior of the license in the domain depends on the supplier of the license. The external behavior of the license should be the same for all licenses. It is highly undesired for the subscription-customer entity to know where the licenses where bought. (That’s where the profits are made
)
To make things more complicated, implementing the behavior of some suppliers will result in dragging in all kinds of code which should be completely unknown to the domain model. All solutions you presented me, including the nice one using lambda’s, would result in just that. For instance: when it comes to the web service based supplier we need things like credentials which are stored in the db. To get those you need the repository. You don’t want the domain model to depend on the repository; that’s even technical impossible. As a repository depends on a domain model that would result in a cyclic dependency.
A factory as man in the middle
These are the main parts (assemblies) of the solution together with the parts they depend on
- Domain model
- Repositories (domain model)
- Communication with supplier1 (domain model, repositories)
- Communication with supplier2 (domain model, repositories)
- UI (domain model, repositories)
As stated before the main problem is how to get the supplier specific data of a subscription into the UI (and other parts) without knowing anything about specific suppliers. Our solution is a subscription factory. Only this factory has knowledge of the specific suppliers. It has references to all underlying layers, that is the domain model, the repositories and all supplier services.
When creating a new subscription object it queries all suppliers for licenses
public static Subscription Subscription(int customerId)
{
var repository = new SubscriptionRepository();
var subscription = repository.Subscription(customerId);
subscription.Licenses = new List<Licence>();
subscription.Licenses.AddRange(LicenceProvider1.GetLicences(customerId));
subscription.Licenses.AddRange(LicenceProvider2.GetLicences(customerId, “UserName”, “Password”));
return subscription;
}
The repository provides the plain db data for a subscription. The LicenceProviderX classes handle all supplier specific things. One class will just dive into our own repositories, the other one dives into the web to talk to the suppliers web service. Adding a new supplier leads to adding another line here.
This factory has to provide post production services as well. To do something with a license, like renewing it, requires talking to the license’s supplier. It’s up to the factory to do the magic of finding out who the supplier was. We use a database Id which is mirrored in an enum in code. (This is the dreaded enum this quest started with)
internal enum SupplierIds
{
Supplier1,
OtherSupplier
}
A helper method FindSupplier takes a license and returns the enum member. Using this the factory has a member to renew the licenses in a subscription
public static void OneMonthMore(Subscription subscription)
{
foreach (var license in subscription.Licenses)
{
SupplierIds supplierId = FindSupplier(license);
switch (supplierId)
{
case SupplierIds.Supplier1:
LicenceProvider1.RenewLicence(1);
break;
case SupplierIds.OtherSupplier:
LicenceProvider2.RenewLicence(1, “UserName”, “Password”);
break;
}
}
}
And there is the switch. And IMHO it is a justified case.
Winding down
I don’t think I have presented you anything exiting at all. Which is IMHO good, because we do have a complicated problem. This solution works well in two ways. It is pretty easy to understand how it works and it is pretty easy to maintain. Adding another supplier boils down to, having written all specific code for that supplier, to adding another case. I hope I have made my point that there is a case for that. No doubt there are more elegant solutions. Please strike your matches. And enlighten me.