|
Thanks for drawing a clear picture of your app and workflow ... it's one we can all recognize too.
I'm about to lead you down a road that is familiar to me. It's not the only road. It's just one I happen to know.
The road is a little rough ... and there are forks. We'll proceed as best we can ... with the clear understanding, I trust, that I am "suggesting", not mandating, a way forward. In many spots I'm thinking out loud. You have been warned!
I start where you started. I like having a read-only repository of reference entities. It needn't be "the main repository" ... it just has to be reachable everywhere.
Each Customer session is in its own sandbox ... just as you explain.
I find this easier to bring off if each session has its own child container within a hierarchical IoC container. Load into the child container those services (such as the Customer Repository) which are singletons-with-respect-to-the-customer-session. That way each customer session gets its own repository, EntityManager, etc. Meanwhile, the services shared across the application, such as the Reference Repository, are accessible through the parent container.
Sounds like you want an Administration Module to update reference data such as "Airline". I wouldn't add or modify characteristics of an Airline within my Customer edit module. I wouldn't expect to be modifying Airline info very often and, I'm guessing, only certain users are authorized to do so.
Same for "TravelPlan" if by that you mean a reference plan shared across the application; if it's the customer's own travel plan, that would be customer data ... maintained in the customer edit module ... and NOT stored in the Reference Repository.
Given that the Reference Repository is read-only, the Admin Module does its own thing with its own EntityManager ... and you want it to communicate changes to reference entities in some way that causes the Reference Repository to be updated ... preferrable without it having to go to the database.
For these and other reasons, you probably want a Reference module associated with your Reference Repository. That module is responsible for maintaining the read-only reference entities. It may be responsible for populating the EntityManagers of child repositories (see below).
It can also provide list services for the entire application; consumers ask it for reference lists that populate comboboxes and such; it might even provide ready-made combobox controls that are easy to "bind" to the entities exposed in the UI.
Your local user isn't the only one who might update Reference entities. Your Reference module could periodically, asynchronously, refresh its entities from the database. (I discuss consequences of this toward the end of this post).
A proper discussion of how best to refresh the reference data from the database is out-of-scope; "best" is a function of too many factors to cover here. For now we'll assume you poll every ... I don't know ... couple of minutes. The point is that the polling frequency is too long to keep up with changes made by the local user. The local user will expect to see her changes propagated immediately.
Therefore, as you suggest, the Reference Repository must be updated by some mechanism other than the slower, remote-refresh cycle and, preferably, without a trip to the server. The mechanics of transfering entities from the Admin Module (which saved the changes) to your Reference Repository are simple as you say: just import them using the EntityManager Import feature.
The more interesting question is "how does the Reference Repository learn about the entities it should update?" In a simple - but tightly coupled approach - the Admin Module would pass the changed entities to the Reference Repository directly.
It certainly has access to the Reference Repository. The Reference repository can provide a method that receives "updated entities". The caller doesn't have to know which entities to give to the Ref Repo; just give it everything you save ... and let the Ref Repo decide which of them it cares about!
You might prefer a more decoupled approach; I know I would prefer a decoupled approach. When a module saves any entities successfully, instead of calling the Ref Repo, it "announces" this fact by publishing an "EntitiesSaved" message with the saved entities in the message payload. The Ref Repo listens for this message and does its thing ... just as if it had been called directly.
You might consider putting the code to raise the message inside the EntityManager.Saved event. That encapsulates the concern nicely and minimizes the risk of forgetting to send the message.
You could use an Event Aggregator as the message broker; many people would ... I would ... and there's an EA waiting for you in PRISM, MVVM Lite, and lots of other such frameworks. Or you could make an EntitySaveTracking service that is dedicated to propagating this message.
Don't dismiss the messaging approach (whether you use an EA or a specialized service). You will have created a mechanism whereby any module can learn about entity saves coming from anywhere. You'll have something more general than just a way to alert the Ref Repo to such changes.
I need to bring up one more issue. We've talked about how the Ref Repo learns of changes to reference entities. Now how will child Repositories learn of and respond to changes in the Reference Repository!
The answer depends upon how child repositories ... or, more precisely, their EntityManagers ... are referring to the reference entities. You have (at least) two choices:
1) Each child repository has a copy of the reference entities. The copies were imported from the Ref Repo when the child repository was created.
2) Entities in child repositories don't use DevForce entity navigation to get to reference entities. They use some form of custom navigation path that leverages the Ref Repo and the entities in the Ref Repo's EntityManager!
You proposed to adopt #1 so I'll defer discussion of #2 to an addendum to this post.
Ok, with #1, you're willing to burn memory with copies of reference entities. This certainly makes entity navigations to the reference entities easy. You don't have to do a thing; Customer.CustomerStatus just works.
However, when reference entities change in the Reference Repository, the child repositories are going to have stale ... incorrect copies. Follow me here:
- Admin Module adds a new Airline
- Admin Module changes the name of an existing Airline
- Customer Edit session 'A' has Airline with the old name and doesn't know about the new Airline
- Customer Edit session 'B' is in the same boat.
What do you do? You can't just fold the Admin Module into the Customer Edit Module ... because you won't have dealt with the real problem ... that other modules (session 'B' in this example) have stale Reference Entity state.
One approach is for each child module to be listening for those saved entities and to have logic to update their own copies of the reference entities. I think this is a terrible idea; it spreads repair logic all over the place.
I'm thinking maybe the child Repositories listen for a message from the Ref Repo saying "I've got updates". They respond by asking the Ref Repo to refresh them ... in the same way they got their ref entities (and lists, etc.) the first time. I'm not sure about this though. I'll may to think about it more; I know that you will :-)
I'm sure we've forgotten something :-)
Addendum: a note on Reference Strategy #2:
#2 doesn't have the problem of propagating reference entity changes to child repositories. The child repositories are actually using the entities resident within the Reference Repo; they are updated automagically.
This is a huge advantage if you anticipate frequent changes to reference entities.
However, you'll have to finagle things to make #2 work. With #2 you can't use Customer.CustomerStatus as DevForce constructs it ... at least not if DevForce generated it in support of a Customer<-->CustomerStatus association in the EF model.
You can't because the CustomerStatus navigation property will look for CustomerStatus in the same EntityManager as the Customer ... and its not there! It's sitting in a different EM ... the Ref Repo's EM.
If you expect reference entity update to be rare, you'll probably prefer #1. Life is much simpler if you can treat reference data as immutable for the lifetime of a client session.
Let's suppose reference entity updates are not rare. What can you do to make Customer.CustomerStatus reach for the CustomerStatus entity in the Ref Repo.
You should be able to inject custom property interceptors into the navigation properties that trade in reference entities. An interceptor of Customer.CustomerStatus would re-route the get and set through the Ref Repo.
I haven't explored this yet myself ... but it holds promise!
|