In the previous post in ORM+IoC series we’ve managed to decouple away from the ORM-based collection and the literal-based syntax (which was bound to the logics of eXpress Persistent Objects ORM).
In this post we’ll try to get rid of all the remaining coupling (while making it unit-testable) and even add domain-based extensibility that is easy to implement.
Let’s modify the code:
public sealed class DisableAllAccounts : ICommand
{
private readonly IOrderedQueryable<IAccount> _accounts;
private readonly ILog _log;
public DisableAllAccounts(IOrderedQueryable<IAccount> accounts, ILog log)
{
_accounts = accounts;
_log = log;
}
public void Execute()
{
_log.Write("Disabling accounts:");
var accounts = from c in _accounts
where (c.Disabled == false)
select c;
foreach (var account in accounts)
{
IComment comment = account.CreateComment();
comment.Text = string.Format("Account {0} was disabled on {1}",
account.FirstName, DateTime.Now);
comment.Save();
account.Disabled = true;
account.Save();
}
}
}
The code is bound just to the .NET framework classes and out interface declarations. That is how they can look for this scenario:
public interface IResolver
{
T Resolve<T>();
}
public interface IAccount : IResolver
{
string FirstName { get; set; }
string LastName { get; set; }
bool Disabled { get; set; }
}
public interface IComment : IResolver
{
int OwnerId { get; }
string OwnerRecord { get; }
string Text { get; set; }
}
These interfaces are the only domain model definitions that you need to define and use in the business code. ORM is completely hidden.
Note, that we do not have any “Save” or “GetComments” methods on these interfaces, but the code just works and IntelliSense presents us with the available options.
If you are familiar with the extensibility model of autofac IoC container, then you’ve probably already got the idea how everything has been implemented.
Domain objects are normal domain objects that can inherit from the ORM-defined base objects, if they need to. They just add the IResolver interface (you could do that in the base class as well):
public sealed class Account : XPObject, IAccount
{
private readonly XPResolver _resolver;
public Account(Session session, XPResolver resolver)
: base(session)
{
_resolver = resolver;
}
public T Resolve<T>()
{
return _resolver.Resolve<T>(this);
}
Since the constructor is resolved in the IoC any way, you can add additional services to it on per-model basis. That’s how Session and resolver get in.
The actual implementation of resolver is bound to the specific ORM we use (XPO) and looks like this:
public sealed class XPResolver
{
private readonly IContext _context;
public XPResolver(IContext context)
{
_context = context;
}
public T Resolve<T>(XPBaseObject obj)
{
return _context.Resolve<T>(new Parameter("target", obj));
}
}
It is a bit similar to the Resolver pattern of abstracting away from IoC, that I’ve talked about before. But there is an important modification in this scenario: resolution calls upon the domain model receive “target” parameter that is the actual model itself.
The classes that we resolve upon the domain model are actually the services that can augment it’s behavior and functionality in numerous ways. Let’s look at the RecordManagement service, for example. That is the service that provides ORM-specific Save, Delete and Reload calls.
public interface IRecordManagement
{
void Save();
void Reload();
void Delete();
}
public sealed class XPRecordManagement : IRecordManagement
{
private readonly XPBaseObject _object;
private readonly ILog _log;
// the limitation
public XPRecordManagement(XPBaseObject target, ILog log)
{
_object = target;
_log = log;
}
public void Save()
{
_log.Write("Saving object");
_object.Save();
}
public void Reload()
{
_object.Reload();
}
public void Delete()
{
_object.Delete();
}
}
Note, that in this service I’ve decided to log every “Save” operation in our system and have added Dependency-Injected logger to do that.
Below is the last piece of code that actually binds together the IAccount interface, Account implementation, IRecordManagement and its Save method into a simple “_account.Save()” visible in the IntelliSense:
public static class RecordManagementExtensions
{
public static void Save(this IResolver resolver)
{
resolver.Resolve<IRecordManagement>().Save();
}
....
Now, a couple of notes:
- All primary interfaces normally should go into the core library, domain models can go into extensions along with the extra resolvers that bring extra functionality to the domain models. C# extension methods get to reside in the same assembly with the interfaces that they extend.
- ORM-specific implementations can be totally separate. The business code does not need to know about them or the ORM in order to compile or be tested.
- We have to implement any extension service only once and then its operations (i.e.: Save, Delete, Reset) will be available to all IResolver descendants (which happen to be all domain models). Single implementation class bound to ORM will have to deal with the specifics.
- Extensibility can be fine-grained by adding more complex hierarchy between the IResolver and the domain model interface. The ORMs behind will have to implement that.
- Theoretically we can switch to any ORMs as long as you have the proper implementation libraries for them. The business code will not notice.
- We will not need to recompile the business code when ORM version changes. Dropping the provider assembly nearby and plugging it in via IoC should do the trick.
- All implementation for the extensibility services must have the “target” parameter in order to get the object in. They can have additional parameters as well; these will be resolved by the container.
Now let us finish with the details. That’s how you have to register any domain model:
dictionary.AddClassInfo(typeof(IAccount),
dictionary.GetClassInfo(typeof(Account)));
builder.Register(c => new Account(c.Resolve<Session>(),
new XPResolver(c))).As<IAccount, Account>().FactoryScoped();
We register account implementations as two services here, since the ORM has to know about the actual type in order to have access to its business-specific members. Additionally we register the interface in slightly patched version of XPO’s ReflectionDictionary just to make it recognize the BO when it is asked to load its interface. Obviously, batch registration should be used to register multiple objects at once.
Record management is registered as Factory, since we have to get a new one per object being managed:
builder.Register<XPRecordManagement>().As<IRecordManagement>().FactoryScoped();
Registration for the query and session is the same as in the sample from the previous post in the series:
builder.RegisterGeneric(typeof(XPQuery<>)).
As(typeof(IOrderedQueryable<>)).ContainerScoped();
builder.Register<Session>(c => new FireSession(type => c.Resolve(type)))
.ContainerScoped();
And here are bits of the code (simplified on a purpose) that allow to extend any domain model (it has to inherit from XPObject) with comments:
public interface IComment : IResolver
{
int OwnerId { get; }
string OwnerRecord { get; }
string Text { get; set; }
}
public interface ICommentExtension
{
IQueryable<IComment> Comments { get; set; }
IComment CreateComment();
}
public sealed class XPCommentManagement : ICommentExtension
{
private readonly XPObject _target;
private readonly IContext _context;
public XPCommentManagement(XPObject target, IContext context)
{
_target = target;
_context = context;
}
public IQueryable<IComment> Comments
{
get { return GetComments(); }
set { throw new NotImplementedException(); }
}
public IComment CreateComment()
{
var resolve = _context.Resolve{Comment}();
resolve.OwnerId = _target.Oid;
resolve.OwnerRecord = _target.ClassInfo.TableName;
return resolve;
}
public IQueryable<IComment> GetComments()
{
return new XPQuery<IComment>(_target.Session).
Where(c => (c.OwnerRecord == _target.ClassInfo.TableName)
& (c.OwnerId == _target.Oid));
}
}
public static class ICommentExtensions
{
public static IQueryable<IComment> GetComments(this IResolver resolver)
{
return resolver.Resolve<ICommentExtension>().Comments;
}
public static IComment CreateComment(this IResolver resolver)
{
return resolver.Resolve<ICommentExtension>().CreateComment();
}
}
Note, that we will be getting “Save” notification in our log for every object being saved (including comments), since all domain objects reuse IRecordManagement service for this operation, and our current implementation for it does take note about that, regardless of the model.
Update: XPO-related changes that are needed to make the ORM+IoC samples work.
PS: Please remember, that this article is just about the prototype and there are a lot of issues that haven’t been addressed (out of the scope). Although, the primary logics seems to be folding nicely. So, if later testing does not reveal any blocking issues, I’d recommend this architecture for the xLim approach. It does feel to be efficient and fits the primary extensibility logic really well.


Many thanks, I’ve found the series culminating in this post really thought provoking.
One thing. You mention… “Additionally we register the interface in slightly patched version of XPO’s ReflectionDictionary just to make it recognize the BO when it is asked to load its interface.”
Is it possible to see the modified ReflectionDictionary code?
– Alex
Alex,
Sure. Please, take a look at the next post.
Additionally, I’ve posted a question about DXperience code sharing here:
http://community.devexpress.com//forums/p/62768/212865.aspx#212865
Best regards,
Rinat
Rinat, many thanks for that.
– Alex
Alex,
you are welcome.
Best regards,
Rinat
Just to summarize your post modulo XPO specifics, here is what I’m gathering: Relative to the issues from earlier in the series, you’ve chosen to use a service locator (exposed via a layer supertype) for IoC within your domain, and continue to use use DI for IoC within your services.
This is a pretty common approach (historically the most common), getting us most of the way there, with the only main set of trade-offs being that your domain objects will have to make Resolve calls in order to avoid hooking into your ORMs domain object instantiation code (and forcing other code to instantiate through factories) and then suffering the overhead of injecting services into thousands/millions/billions/etc. of domain object instances that may not even end up needing their services.
The more I’ve thought about this over the last several days, the more I’m starting to think that the perf overhead of the more quote/unquote “elegant” approach using 100% DI may be worth avoiding at the cost of having to make Resolve calls. With that in mind I’ll probably focus my efforts on cooking up nice, lean, (deferred!) field initializers so that at the very least the Resolve calls can be nicely centralized at the point where each service instance field is declared in each domain class.
PS - This will probably be done as simply as by declaring a nice little “protected T Resolve()” method in my domain layer supertype and invoking it in my field initializers, as they’ll each only be invoked upon first usage of each field. If any domain objects need to survive across container scopes, they can avoid using the fields and (at the cost of a bit more typing) do per-usage Resolve() calls any time they need access to a service.
PPS - Just pretend that the Resolve calls have a type specifier on them, as it got eaten each time by the comment engine.
Jeremy,
thank you for your comprehensive comments.
Yes, moving the Resolve calls back to the super-type (along with making it protected) seems logical. Free DI-related methods with unbound service location could be really dangerous in big development teams.
And if somebody ever needs to deliver a domain object library, that is easy to extend, they can just make the Resolve public.
About the performance. Personally I’m not sure yet if using some complex logic for field initializers is worth it in the long run. That will be some repetitive code out there.
If it were possible to create some dictionary-type quick adapter on this Resolver interface (to cache singleton and container-scoped instances) then the performance could be appropriate for the majority of UI-related usage scenarios. However, I personally haven’t even profiled autofac itself.
Surviving across container scopes seems a little bit tricky for me now. In series usage scenarios (and in my projects) session always has the life cycle of the container it is in. And so are the BOs that were created with it. There could be child containers below (using the same session or UoW override, and/or BO instances from the upper container). But when this container dies, then logically everything else within it should be already on its way to the GC.
Jeremy, please, keep me updated of your findings, if you do not mind!
BTW, do you have weblog?
Best regards,
Rinat