With S#arp Architecture, my understanding is that domain logic (aka business logic) that operates on more than one type of entity is best handled by the Application Services layer.
So the classes in Application Services will need access to the Repositories. Presumably then you inject the Repositories in via the constructors. Because there is one class of repository per entity type, any fairly realistic task is going to need access to several repositories. So you might have an Application Services class looking like this:
public class DogTasks
{
public DogTasks(IRepository<Dog> dogRepository,
IRepository<Trick> trickRepository,
IRepository<DogTrick> dogTrickRepository,
IRepository<Lesson> lessonRepository)
{
// etc
}
public void TeachDogNewTrickAtALesson(int dogID, string trickName, int lessonID)
{
// etc
}
// other methods, etc
}
This Tasks
class can then be injected into the relevant Controller.
So far, I think I get it. But I am perturbed by the following:
When I need a new Application Services method that uses a combination of repositories that I don't have yet, I have to choose between changing the constructor for one of my existing classes to accept the new repositories, or starting a new class altogether. Adding arguments to constructors upsets a lot of the开发者_开发技巧 unit tests, but proliferating new classes doesn't seem good either.
When Controllers need to do simple Repository operations (like a
get
) it makes sense to inject the repositories into the Controllers as well as the Application Services classes. But then I get the same 'changing constructor arguments' issue. The other alternative seems to be to only let the Application Services layer play with the Repositories, but then you get a lot of boilerplate code added to the Application Services to do very simple things.
These sorts of things make me think I might be doing it wrong. So how should a good Application Services layer be organised?
e.g. Do you have lots of classes that just do one task each? Or do you clump related tasks together along entity lines? How do you deal with tasks that need a lot of repositories? Does needing a lot of repositories for a task mean its time to go back to the drawing board?
First, I'd like to counter your assumption that each entity needs its own repository. Per, Eric Evans "Domain Driven Design"
Repositories give access to selected aggregate roots. Repositories are prohibited from the interior of an aggregate.
Given your example, a dog has a set of tricks that it has learned. When you want to add a new trick to the dog, you'd do something like this:
var dog = dogRepository.Get(dogId);
dog.Tricks.Add(newTrick);
dogRepository.SaveOrUpdate(dog);
When I need a new Application Services method that uses a combination of repositories that I don't have yet,
I'm not sure what you mean by this. But I think if you stick to using repositories for aggregate roots, you're not going to run into such messy code.
The other alternative seems to be to only let the Application Services layer play with the Repositories, but then you get a lot of boilerplate code added to the Application Services to do very simple things.
Controllers orchestrate. Think of controllers as a part of the UI, they move you from page to page. I will admit that for simple things, it seems simpler to just inject a repository into the controller, but when your project grows the separation will help a lot, especially if you end up having another application hook into your Tasks layer. Keep repositories out of controllers.
e.g. Do you have lots of classes that just do one task each? Or do you clump related tasks together along entity lines? How do you deal with tasks that need a lot of repositories? Does needing a lot of repositories for a task mean its time to go back to the drawing board?
Again, I think this goes back to defining aggregate roots. Having 4-5 repositories in a task isn't that big of a deal. I usually organize my tasks by what the application is trying to do, with the idea that if the UI changes to, say, an external JSON request, you just need to call the right task.
Hope this answers your question. Feel free to post this on the Sharp mailing list, you might get a better response there.
Edit based on comments:
Check out Who Can Help Me (https://github.com/sharparchitecture/Who-Can-Help-Me) for an example of how to use the ApplicationServices/Tasks layer. They have a fairly small domain model so each entity has its own task.
I think you're confusing terminology a bit, or perhaps I'm being unclear. The idea behind an ApplicationServices layer is to further abstract the UI from the domain layer. Repositories are domain layer entities, and knowledge of them should not be in the controller. If you end up swapping out ORM or even moving to a document-based storage system, you'll see why this abstraction makes it really convenient, you just need to make sure your ApplicationServices contracts are working and don't have to muck about in the controllers.
But, don't confuse the need for ApplicationServices as a way of future proofing. It simply allows for further decoupling between your layers and decoupling is nearly always a good thing.
Again, for a project you're working on solo, all this might seem a bit of overkill. When you're working with other developers, all this abstraction is really, really nice. You can have a team working on upstream domain issues, and a team working on the presentation layer, and have a nice separation of concerns.
Have you heard about Abstract Factory pattern? It solves this problem in a nice way:
public interface IDalFactory
{
// One way
IRepository<Trick> TrickRepository { get; }
IRepository<Dog> DogRepository { get; }
...
// Other way
IRepository<T> GetRepository<T>();
}
public DogTasks
{
public DogTasks(IDalFactory dalFactory)
{
...
}
}
It is up to you how do you implement IDalFacotry
. I usually using lazy initialization of repositories. Once repository is created it is internally stored and reused. One factory instance is created per http request.
The cons is that you don't have control over factories exposed to your application service. But that are your choices. Adding new repositories to constructor or using factory.
精彩评论