Author |
Share Topic Topic Search Topic Options
|
JohnBloom
Groupie
Joined: 30-Nov-2010
Location: Topeka, KS
Posts: 95
|
Post Options
Quote Reply
Topic: Repository Organization Posted: 06-Jun-2012 at 10:27am |
As we have been using repositories they have slowly become unorganized and we are starting to duplicate crud operations (mostly queries). We were creating one repository per scenario but that is what seems to have lead to duplication as some scenarios share the same tasks. Now that we have unit of work I am thinking that we need to tackle reorganizing our repositories so we have a logical way reusing code.
My thought is to have one repository per type and use UOW to bring them together for the scenarios. Is this the correct way to go about this? What things should we keep in mind as we create repositories and how should we go about simplifying? I looked through the documentation and there doesn't seem to be any guidance on repositories or unit of work. Maybe I just need to polish up on my Patterns and Practices :).
|
-John Bloom
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 06-Jun-2012 at 11:11am |
John,
Yes, if you use UoW you'll have one repository per type and one factory per type and zero or one domain service(s) per UoW. The UoW then brings together all the repositories and factories for all the types that are involved in a particular type of UoW plus the necessary domain services for the business logic/processes. In TempHire for example there's only one UoW and it brings all the repositories and factories together. TempHire doesn't have any domain services, because there aren't any business processes at the moment.
Documenting the whole UoW stuff is an outstanding item on my list. I'm still toying with the current implementation. You may have noticed that additional functionality has been added.
|
|
JohnBloom
Groupie
Joined: 30-Nov-2010
Location: Topeka, KS
Posts: 95
|
Post Options
Quote Reply
Posted: 06-Jun-2012 at 11:26am |
Thanks Marcel. The reason I mentioned documentation is because I try to look there before asking questions. I know you are a busy man and I value your keystrokes. I am not familiar with exactly what you mean by domain services. Could you point me to a reference on that? It sounds like understanding them might be a key to cleaning up some processes that have become dirty and hard to manage.
|
-John Bloom
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 06-Jun-2012 at 12:48pm |
I appreciate that. It's good to know somebody is reading the documentation :-).
I would have to dig up a good reference, which will take longer than trying to explain it. Domain services is a wide term and essentially captures everything. Repositories and Factories are technically domain services as well. In this case though the domain services are anything that isn't a repository or factory. These are most often cross entity business processes and business logic.
For example, let's say we are building an application that transfers money from one account to another account. So, we would have a unit of work type representing this workflow. The unit of work would have an account repository, so we can retrieve accounts. But where should the transfer logic go? This is a case for a transfer service on the UoW.
We would create a stateless transfer service with a method called Transfer that takes the account number for the source account, the account number for the destination account and the amount you want to transfer. The service would then use the account repository, which it was given by the UoW, to retrieve each account, check any applicable business rules, deduct the amount from the source account and add it to the destination account. When all is good, you ask the UoW to commit the changes. You typically expose all these domain services as properties on the UoW and it is the UoW's responsiblity to configure them, so they work within the confines of the current UoW.
|
|
Walid
Senior Member
Joined: 14-Nov-2010
Posts: 161
|
Post Options
Quote Reply
Posted: 06-Jun-2012 at 3:57pm |
Do we have to use a stateless transfert service for this, can't we just use a method on the Account entity to do the transfert ? Or shouldn't we call a method on the server to do the transfert ? With cocktail it looks like most of the business logic is on the Client side and nothing is on the Server. This is disturbing me quite a bit. When I look at exemples on the repository/factory/Domain services pattern with entity framework I see them on the server side not on the client side like it is with Cocktail. What happen if tomorrow I want to target another framework ? I feel I will have to reimplement them because most of my business logic will be in SL and not on the server.
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 06-Jun-2012 at 4:42pm |
The issue with implementing this as a method on the Account is who loads the second account? Before you call the method, something has to load the second account and potentially some of its related entities. You end up with your logic being spread around. Part of it will be in the ViewModel and the other part in the Entity.
It is generally accepted to put logic on the Entity if the logic is self-contained, meaning it doesn't have to step outside of the aggregate. All the data the logic needs is either in the root entity or in a related entity that can be accessed via navigation. That's not the case here. This logic involves two accounts that are not directly related. Even if your logic is constrained to the aggregate you have an issue in Silverlight, because navigation is asynchronous. What if the related entity is not in the cache? Now your logic is making assumptions on the state of the cache. You'll find yourself in a ditch pretty quickly if you rely on such assumptions. The first sign of trouble is when entities are being passed around instead of IDs. Every time you are being passed an entity you have to rely on all data to be there. That's dangerous business. Even in the synchronous world this is an issue. It leads to accidental roundtrips and the infamous n+1 problem. I see it happen all the time when a customer calls and complains about performance. Developers made assumptions about what is in the cache. The assumptions may have been correct 6 months ago when the logic was first implemented, but things have changed, new requirements have been added another developer has refactored something and all the sudden your performance goes down the drain, because you are unaware of the hidden roundtrips. Often as a quick fix, developers start adding Include after Include and soon bring down massive graphs that make things even worse. Trust me, I'm currently sorting through exactly these issues with a customer and it's not the first time.
Implementing this as a stateless service puts everything in one place. The service conditions the cache for the logic and then performs the logic. If you need to refactor you have one place to refactor. No assumptions on what the current state of the cache is.
Yes, most of the logic is on the client. This is the paradigm of RIA. Rich clients leveraging the power of the client device. Done right, this yields much better performing and more tolerant applications, than the traditional apps that rely on services on the server to perform logic. In a true RIA app you can still do stuff even if you have intermittent connection to the server. It's the offline kinds of capabilities that make the app much more tolerant to network and server outages. The downside is that if you change platforms completely as in moving from C# to Javascript then you'll have to reimplement your logic. The question is what is more important to you and your customers? Generally the customer cares about the experience and performance of the application today and not how easy it may be for you to port it to another platform if that time comes.
|
|
JohnBloom
Groupie
Joined: 30-Nov-2010
Location: Topeka, KS
Posts: 95
|
Post Options
Quote Reply
Posted: 07-Jun-2012 at 6:40am |
Up until now we have been putting all of the domain service type logic in the repositories. I think that I am starting to get this. It would be nice to eventually have a sample in TempHire.
For now let me repeat this so that I make sure I got it. The Domain Service is just a class that contains very specific business logic. It doesn't import a EntityManagerProvider but is instead passed the repositories that it needs to complete its processes. Would you pass the Repository into the constructor or would that be considered adding state since you would need to set it as a property in the class?
As i see it Domain Services are where a lot of the co-routines we have will end up because they are usually putting together different pieces to form a process. We had planned on putting those in the UOW itself but this makes more sense.
One comment on the server vs client thing. If our logic is written so that it can target both Silverlight and WPF would it be that hard to move it to the server in the case that we switched to javascript? In our switch we could use the logic on the server and then only write the javascript when we see a performance hit on the client. It would put some business logic in javascript and some in c# but it would save having to rewrite everything. Am I crazy to suggest this?
|
-John Bloom
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 07-Jun-2012 at 10:28am |
Yes, repositories and entities tend to get overloaded. You were hinting at that being the case here in your app. Repositories really shouldn't have much more than what you currently see in my IRepository<T>. I'm even considering removing the import methods. They were a contribution from a customer, but they tempt you down the path of passing around entities. The only other thing that could get added is the ability to run paged queries. Other than that, a repository retrieves a particular entity type by ID or predicate, that's pretty much it.
Yep, you got the idea. Yes, you pass the repositories into the constructor. You may even need to pass factories if your logic involves creating new root entities. Related entities should be created through the root entity as demonstrated in TempHire. Remember, the repositories and factories are services themselves, so you are not adding state to your domain service. You are giving it other services it needs to perform it's function. The EntityManager which holds the state is hidden behind the repository.
To your last point, server and client are really different. The client should be all asynchronous even in WPF. Notice how my UoW implementation is all asynchronous. It's the future and synchronous apps simply suck. WinRT/Metro just like Silverlight won't allow synchronous server calls in order to keep the UI thread free to respond to touch input from the user. Blocking the UI thread while retrieving data is a bad user experience and leads to poor performance.
On the server the world is synchronous, because the server already is multi-threaded. Each request runs on its own thread. So, you have fundamentally a different usuage paradigm on the client vs the server. You could break out the core logic, which acts on the entities once they are loaded. That core can be shared between server and client. You simply front it with asynchronous prepartion logic on the client and synchronous preparation logic on the server.
On the other hand the new Javascript clients that everybody is talking about now are still RIA. These are not web sites with a little AJAX sprinkeled in. What you are doing in XAML and C# today, you'll be doing exactly the same in HTML5 and Javascript, just a lot less productive for a while, because there is nothing like Cocktail today or like DevForce. Microsoft just pulled the plug on upshot.js and are going to rebuild it, so all the MVC 4 stuff coming out won't have a way to query/save data from a client.
|
|
Walid
Senior Member
Joined: 14-Nov-2010
Posts: 161
|
Post Options
Quote Reply
Posted: 08-Jun-2012 at 2:59pm |
marcel,
Since I followed your suggestion to break up the many to many relationship I don't need anymore the Import method. If you think it isn't usefull in the UnitOfWork feel free to remove it.
|
|
paul
Newbie
Joined: 16-Sep-2010
Posts: 19
|
Post Options
Quote Reply
Posted: 20-Jun-2012 at 8:02am |
I am curious on the UoW pattern on a large SL application. For the last few months I have been using VM --> Repository paradigm. It is similar to the TempHire one before the shift to UoW, pre v0.5. After following another thread in this forum I to am starting to see my repository classes start to get unwieldy.
The TempHire sample is excellent but my biggest issue with it is it only has one main component, StaffingResource. There is one very large form (Resource Management) made up of many smaller VMs and Views. The whole application has one UoW for the entire application. My question is how do you manage the UoW paradigm when you multiple modules and forms?
For example we have an application with two modules: Employees and Customers.
The Employee module has many forms: Dashboard/Overview, Details, WorkHistory, Education, Messages, etc.
The Customer module would be laid out in a similar fashion.
In the case of TempHire you have these forms displayed as tabs in the main StaffingResource form. In our app these tabs are broken out into separate forms. Would each form have its own UoW or a single UoW for the entire module. I could see case for both ways. A single UoW would work since in a way all these forms are tied to a single Employee entity but like my current repository could get super large and I am newing up objects my form wouldn't use. If I go with each form having its own UoW then every form has it own EntityManager and I feel that gets too granular.
Any opinions? Thanks...
|
|
JohnBloom
Groupie
Joined: 30-Nov-2010
Location: Topeka, KS
Posts: 95
|
Post Options
Quote Reply
Posted: 20-Jun-2012 at 8:44am |
Marcel is the guru but I can tell you what we are doing.
We have a UoW per main form. For us that would be a menu item. We have a customer details form for updating customer information and that all goes in its own unit of work. If you put everything in its own UoW you are negating all of you efforts to encapsulate your domain logic. In Customer details we dont need access to the dashboard logic so it is not in the UoW.
We were in the same boat that you are in. Our repositories got really unmanageable and when we brought on a new guy we found out that they are not very trainable. The UoW pattern at least gives us a place to put everything and for simple forms using IUnitOfWork<T> makes it so everything is done generically and so it saves you a ton of code. I deleted whole repositories when I moved some simple CRUD screens to UoW.
TempHire only deals with one main form so it makes sense that it only has one UoW. Maybe in the future Marcel can add another main screen to show that his stuff is truly able to handle two forms instead of one.
|
-John Bloom
|
|
mgood
IdeaBlade
Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
|
Post Options
Quote Reply
Posted: 20-Jun-2012 at 10:35am |
Thanks for the post John. That pretty much covers it. There's a couple of things I wanted to add. We should differentiate between UoW types and UoW instances. As John says you want to go with one type per subdomain, whatever the subdomain is in your case. John has broken up his application by main forms. A subdomain could also be an entire module, a XAP file or however you describe a subdomain in your application. You may even want to consider breaking up your domain model into smaller subdomain models.
Now, that you have the types, how many instances should you create? Which VMs should share one instance and which VMs should use their own instance? The answer comes down to your application requirements. A UoW instance represents a single transactional changeset in your application. You save it as a whole or roll it back as whole. Think of it as a task. In TempHire, the UoW instance lives at the StaffingResourceDetailViewModel level. Every VM below that shares the same instance. The task in TempHire is defined as creating/editing/deleting an entire StaffingResource with all it's related entities. The main version of TempHire only allows for one StaffingResourceDetailViewModel at a time, so effectively you only have one UoW instance in flight. It's actually not quite true, because the search grid at the top uses it's own, so in fact you always have two instances in flight.
The MDI version of TempHire ( http://sdrv.ms/KNZkJz) on the other hand allows you to open multiple StaffingResources at a time and edit/save them independently. Each tab with it's subtabs in this case gets it's own UoW instance. Now you can truely have multiple changesets in flight and save or roll back each one independently of the others.
If you start breaking out VMs into seperate forms you may do that because your application requirements dictate that different parts of a larger aggregate must be editable independently. If that's the case, then you would have a new UoW instance at those points. Wherever you need an independent saveable changeset, that's where you use a new UoW instance.
Lastly, if you are worried about the UoWs instantiating all the repositories, factories and services, even though they might not all be used at any given time, you can always implement your UoW to lazy instantiate them instead of instantiating everything in the constructor.
Edited by mgood - 20-Jun-2012 at 10:40am
|
|