Author |
Share Topic Topic Search Topic Options
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Topic: Implementing Domain Driven Design with Devforce Posted: 01-Jul-2013 at 1:42am |
We are trying to Implement DDD with Devforce 2012.
So we have several Domain Models (e.g. Customer, Sales, Finance) and several EntityManagers for each Domain Model.
Now we are facing the situation, where we need to update one Manager from within another manager with the EntityManagerDelegate SaveInterceptor.
What we are trying to achive, is to seperate out the related Business rules and Validations into each Manager and Model, but at the Save Interceptor, we need to update more than one EntityManager (e.g. if a customer is created, the Sale Manager need to create some empty tables with the related customer). This has to be a transactional Save.
Here is a little Sample:
public class CustomerContextDelegates : EntityManagerDelegate<CustomerContext> { public override void OnSaved( CustomerContext context, EntitySavedEventArgs args) { if (args.CompletedSuccessfully) EventFns.Publish(new SavedMessage(args.Entities)); }
public override void OnSaving( CustomerContext context, EntitySavingEventArgs args) { if (args.Entities.OfType<Customer>().Any()) { var sut = args.Entities.OfType<Customer>().Select(x => x).FirstOrDefault();
var businessProcess = new CreateNewCustomer(sut, context, args);
//TODO: Here we need to create a SalesContext Manager and ensure, that both managers get saved transactional. } }
|
Is there a way to create a new EntityManager in the SaveInterceptor and ensure, that both EntityManagers gets saved in an Transaction?
(e.g. only insert a customer in the CustomerEntityManager if the SalesEntityManager has created the corresponding empty Sales entitys)
|
|
gregweb
DevForce MVP
Joined: 10-Sep-2009
Location: Clearwater, Fl
Posts: 253
|
Post Options
Quote Reply
Posted: 07-Jul-2013 at 6:04pm |
Cypher,
I think you are mixing up the SaveIntercepteor, and the EntityManagerDelegate.
The SaveInterceptor is on the server, while the EntityManagerDelegate is on the client. But they are completely different classes and uses.
In the OnSaving, there is no reason you couldn't just new up a different EntityManager if you need to work with it.
For example, in OnSaving:
var salesEntityManager = new SalesEntityManager;
var salesEntity = new SalesEntity();
salesEntityManager.AddEntity(salesEntity);
salesEntityManager.SaveChanges();
But this is not recommended as it is completely outside the transaction and the whole Cocktail Unit-Of-Work Pattern.
I too run into the issue of wanting different entity managers for different parts of the App, but if there are relations, it becomes difficult to work with the separate managers just like the case here.
If you look at TempHireEntities, and your using CodeFirst, it's just a list of EntityQueries. Maybe you could just add the query you need to get it all in the same EntityManager and then you would get a transaction. Haven't tested this, but I don't see why it wouldn't work.
Hope this helps.
Greg
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 08-Jul-2013 at 1:04pm |
Just to add to what Greg wrote. The EntityManager is the transaction boundary on the client. Everything that needs to be saved as part of the same transaction needs to be created in the same EntityManager. This extends to the UoW in Cocktail. The UoW has a one-to-one relationship to an EntityManager.
You can have multiple EntityManagerDelegates per EntityManager type on the client if you want to split up some of the logic, but all logic needs to operate on the same EntityManager instance.
In the SaveInterceptor on the server you can create/modify additional entities and have them save as part of the same transaction.
As a final note, an EntityManager has no direct relationship to a particular model. Any EntityManager can query any entity regardless in what model the entity is defined. You can even mix entities from different models and physical databases in the same EntityManager and they all get saved in the same transaction and if necessary the transaction gets escalated to a distributed transaction.
You should think of the EntityManager as a bucket for all your current changes that you gonna save together. This point is really driven home with the UoW pattern. Don't think of the EntityManager in any way associated to a particular model.
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 08-Jul-2013 at 1:16pm |
On a releated note, I get the feeling that you are incorrectly using the EntityManagerDelegate. The kind of business logic you describe belongs into services and factories on the unit of work.
|
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Posted: 09-Jul-2013 at 7:49am |
Hi Marcel,
we only need to create the entitys, if the user decides to save. Then we start Business Processes, that update, modify and delete enititys, that are not directly related to our unit of work. But depending on the Changes in our Unit of Work, we have to modify different tables.
Therefore we are trying to let this complexity out of the UoW and implement this Workflow logic, only if the user has changed some entitys, that need to start a business process.
We found out, that we can still have this seperated out into different Models and Managers and Create, Modify or update Entitiys with different EntityManagers. We just have to add the Changes not only to newly created EntityManager, we also need to tell the EntitySavingEventArgs to save the changes.
Currently we are dealing with business processes, that that check for changes in the UoW and start a Business Process, that maybe need to add entitys with another EntityManager and Model. Therefore we have created the Extension Method, where the EntitySavingEventArgs are now filled with more than one EntityManager, but still get saved with distributed transaction (because our models have different connection strings).
namespace Common.Extensions { using IdeaBlade.EntityModel;
public static class EntityManagerExtensions { #region Public Methods and Operators
/// <summary> /// Attaches a new Entity from within the Save Interceptor /// </summary> /// <param name="manager">The manager.</param> /// <param name="entity">The entity to be added, updated or deleted.</param> /// <param name="savingEventArgs"> /// The <see cref="EntitySavingEventArgs" /> instance containing the event data. /// </param> public static void AddEntity( this EntityManager manager, object entity, EntitySavingEventArgs savingEventArgs) { manager.AddEntity(entity); savingEventArgs.Entities.Add(entity); }
#endregion } }
|
Right now it perfectly works, and in the UoW, we really care only about "simple" changes. But in the Save interceptor, we verify, what has been changed, to start additional business processes.
here is a sample of our Code
if (args.Entities.OfType<Vehicle>() .Any()) { var vehicle = args.Entities.OfType<Vehicle>() .Select(x => x) .FirstOrDefault();
CheckDamageFlags(vehicle); CheckNewUnitFlag(vehicle);
Composition.GetInstance<IChangeRemarketingChannelAndSaleRelation>() .Start( vehicle, args) .Execute();
args.Cancel = !CheckIsVinUnique(context, vehicle); }
|
Do you think, this is an anti pattern?
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 09-Jul-2013 at 11:59am |
I'm still having difficulty with your using of multiple EntityManagers. In order for any entities to be saved properly, they must be in the cache of the EntityManager that is performing the save. I'm not clear on your extension method above and on what EntityManager you are calling it as well as why you need multiple EntityManagers for this in the first place.
The one thing that stands out from your approach is the fact that you can't perform any asynchronous calls in your logic if you place it inside of the EntityManagerDelegate. Sooner or later one of your business rules may need to query for additional data. At that point you have to query synchronous which is gonna negatively affect the responsiveness and performance of your app.
The proper approach for adding logic during a save would be to override CommitAsync in the unit of work, where you can perform synchronous as well as asynchronous logic before the save is executed.
|
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Posted: 10-Jul-2013 at 6:37am |
Hi Marcel,
i'm glad you ask.
We are really trying to Implement the DDD approach with a Bounded (DB) Context (Context = EntityManager). We are working in an Enterprise Environment with a Database with more than 100 Entitys and some complex dependencies.
To break this complexity a little bit up, we are trying to have a bounded Context.
With this information in mind... se split our implementation of cocktail into three major layers.
and implemented several Domain Models with the corresponding Domain Services:
Now you can see, that we are dealing with several EntityManagers (Contextes) from within on Module. But only during the Save Process, we need Access on the different Models and Managers.
Putting this logic all into on Big Unit of Work, is not really a Domain Driven Design implementation. But at some point, we will not come around to have some duplication. But for now, we where able to live with the different EntityManagers and get them together, wenn the Business Processes starts.
E.g. Saleresults need to be captured, after the user has chanded some trigger informaion on the Main Entity in the Vehicle.
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 10-Jul-2013 at 9:22am |
Cypher, This is all good as far as DDD goes. I'm very familiar with DDD. I think the confusion comes from the fact that you are thinking of the EntityManager as the context. The EntityManager is at a higher level. It represents the business process by extension of the unit of work. The relationships are as follows:
Business Process 1---1 UoW 1---1 EntityManager 1---n DbContext(s)
For each distinct business process in your app you assemble a unit of work with the right repos, factories, services etc. and that's where you implement all the logic for your business process. The UoW will use one EntityManager, which in turn may use multiple DbContexts if the business process is cross domain.
You don't even necessarily need to subclass EntityManager. You can use the out of the box EntityManager class as is.
Edited by mgood - 10-Jul-2013 at 9:24am
|
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Posted: 11-Jul-2013 at 12:27am |
Hi Marcel,
thanks for Sharing this informations.
For me the EntityManager was clearly the generated Context... like it is in EntityFramework.
but we are having a difficulty to live with the Syntax of get query, especially, if you have on entity in multimpe models (e.g. The Customer in the samples above).
A Codesample how to deal with this situation in conjuntion with cocktail would really help us understand your way of implementing DDD.
Thanks in advance for your advices to the community.
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 11-Jul-2013 at 12:06pm |
I'm not sure what you find difficult about the GetQuery syntax. In Cocktail it's even abstracted in the Repository class. You rarely have to use GetQuery directly as fetching data should always occur through a repository which can optimize the fetching strategy based on the use case. Maybe you can elaborate some more.
The same entity in multiple models comes up regularly. The reality generally is that if you have the same entity in different models they are used for different purposes. It is recommended that only one model is used to create/edit that entity and the other models simply use it for read-only purposes and should only map the relevant parts. If multiple models edit that same entity the application is lacking a clear separation of concerns.
For example in the Customer case you may have a model for customer maintenance. It has the full customer entity mapped, so you can create new customers and edit existing customers. Then you may have an Invoice model for example. In the invoice model the customer is used for a different purpose. As far as an invoice is concerned you probably only need the customer Id, name, billing address and payment information, so the customer entity in that model should only map those parts. Then you may have a shipping model. In that model the customer probably only needs id, name and shipping address. The billing address and payment information is irrelevant for shipping purposes. It helps if you don't use the same name for the Customer entity in every model. For example you could call it Customer, InvoiceCustomer and ShippingCustomer to clearly delineate their use.
As far as the unit of work goes you would have the appropriate repositories in there so that if you do need to retrieve the full customer entity in the Invoice module for example you can always do so by customer id. Keeping the majority of your logic/rules external to the entities in services and perhaps a rules engine will make it easier to deal with multiple representations of the same entity.
To really show this in an example application would take a lot of time and complexity and every situation is slightly different. Our professional services organization has written or helped writing very large applications with DDD. This topic is now crossing over into architecture consulting and that's beyond the scope of this forum. Perhaps I can convince you to get in touch with our professional services organization, who could help you come up with the right approach for your situation.
|
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Posted: 12-Jul-2013 at 3:43am |
Thanks again for your detailed answere.
We have started to rip out our addidtional Service layers and try to implement the Different Models with one EntityManagers.
But we are having a hard time, to understand how you would implement different Models within the UnitOfWork, because you have already injected a specific IEntityManagerProvider<T>, that has only Access to one Model.
[Export(typeof(IResourceMgtUnitOfWork)), PartCreationPolicy(CreationPolicy.NonShared)] public class ResourceMgtUnitOfWork : UnitOfWork, IResourceMgtUnitOfWork { [ImportingConstructor] public ResourceMgtUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<TempHireEntities> entityManagerProvider, [Import(AllowDefault = true)] IGlobalCache globalCache = null) : base(entityManagerProvider) { AddressTypes = new GlobalCacheRepository<AddressType>(entityManagerProvider, globalCache); States = new GlobalCacheRepository<State>(entityManagerProvider, globalCache); PhoneNumberTypes = new GlobalCacheRepository<PhoneNumberType>(entityManagerProvider, globalCache);
RateTypes = new GlobalCacheRepository<RateType>(entityManagerProvider, globalCache); StaffingResourceFactory = new StaffingResourceFactory(entityManagerProvider, AddressTypes, PhoneNumberTypes); StaffingResources = new StaffingResourceRepository(entityManagerProvider); VehicleRepository = new VehicleRepository(entityManagerProvider<needToQueryAnotherModel>)
Search = new StaffingResourceSearchService(StaffingResources);
}
|
We dont see a way to extend the interfaces, an tell on repository to Query agains another Model.
Or do we need to Write our own Repository to override some methods.
We see our self chained to one EntityManager with one Model.
Edited by cypher - 12-Jul-2013 at 3:43am
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 12-Jul-2013 at 9:43am |
Again, the EntityManager has no direct relationship to any of your models. Remember what I said before. Any EntityManager type can query any of your models. So, you simply create a repository for the entity type you need. It doesn't matter what type of EntityManager you hand to the repository.
[Export(typeof(IResourceMgtUnitOfWork)), PartCreationPolicy(CreationPolicy.NonShared)] public class ResourceMgtUnitOfWork : UnitOfWork, IResourceMgtUnitOfWork { [ImportingConstructor] public ResourceMgtUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<TempHireEntities> entityManagerProvider, [Import(AllowDefault = true)] IGlobalCache globalCache = null) : base(entityManagerProvider) { AddressTypes = new GlobalCacheRepository<AddressType>(entityManagerProvider, globalCache); States = new GlobalCacheRepository<State>(entityManagerProvider, globalCache); PhoneNumberTypes = new GlobalCacheRepository<PhoneNumberType>(entityManagerProvider, globalCache); RateTypes = new GlobalCacheRepository<RateType>(entityManagerProvider, globalCache); StaffingResourceFactory = new StaffingResourceFactory(entityManagerProvider, AddressTypes, PhoneNumberTypes); StaffingResources = new StaffingResourceRepository(entityManagerProvider); Search = new StaffingResourceSearchService(StaffingResources);
VehicleRepository = new Repository<EntityTypeFromAnotherModel>(entityManagerProvider); }
Better yet, just forget about the subclassed EntityManager and go with the out-of-the-box EntityManager.
[Export(typeof(IResourceMgtUnitOfWork)), PartCreationPolicy(CreationPolicy.NonShared)] public class ResourceMgtUnitOfWork : UnitOfWork, IResourceMgtUnitOfWork { [ImportingConstructor] public ResourceMgtUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<EntityManager> entityManagerProvider, [Import(AllowDefault = true)] IGlobalCache globalCache = null) : base(entityManagerProvider) { AddressTypes = new GlobalCacheRepository<AddressType>(entityManagerProvider, globalCache); States = new GlobalCacheRepository<State>(entityManagerProvider, globalCache); PhoneNumberTypes = new GlobalCacheRepository<PhoneNumberType>(entityManagerProvider, globalCache); RateTypes = new GlobalCacheRepository<RateType>(entityManagerProvider, globalCache); StaffingResourceFactory = new StaffingResourceFactory(entityManagerProvider, AddressTypes, PhoneNumberTypes); StaffingResources = new StaffingResourceRepository(entityManagerProvider); Search = new StaffingResourceSearchService(StaffingResources);
VehicleRepository = new Repository<EntityTypeFromAnotherModel>(entityManagerProvider); }
public class EntityManagerProviderFactory { [Export] public IEntityManagerProvider<EntityManager> TempHireEntityManagerProvider { get { var provider = new EntityManagerProvider<EntityManager>(); #if FAKESTORE provider.Configure(config => config.WithConnectionOptions(ConnectionOptions.Fake.Name)); #endif return provider; } }
|
|
cypher
Newbie
Joined: 26-Nov-2012
Location: Deutschland
Posts: 20
|
Post Options
Quote Reply
Posted: 12-Jul-2013 at 11:16pm |
Marcel,
Thank you so much. We really had a hard time to let our brain get open for the idea of a default EntityManager.
We could't imagine, thats this would work.
With this idea in mind, we are now capable to implement the Domain Driven Design within our Application and keep the Models much cleaner.
Really
Thank You... looks all so easy, but makes a big difference for us.
|
|