I am developing a 3-tier application (not 3-layer!) with a client application running on one tier (physical cluster) that interacts with a service application running on another tier and the database server on yet another tier. The application has a lot of business rules, process logic, etc. that I believe should be available on both the app and service tiers to improve the user's experience, reduce calls to the service as well as eliminate redundant coding.
Let's use this example: In my domain layer, I have a Document object. This object contains an AllowPublish property which examines the internal state of the object and returns true/false if the state allows the document to be published. The object also has a Publish method which modifies the internal state of the object to reflect the fact that it is being published by setting the IsPublished flag to true and raising the Published domain event.
I have a separate AuthorizationService which determines if the current user is allowed to publish as well as a DocumentRepository which persists the object to the database.
In my service application, my DocumentService has a PublishDocument method that accepts the document id, retrieves the document from the repository using the id, checks the AllowPublish property and, if true, calls Publish then persists the updated object using the repository.
I have slightly different behavior on the client. In that case, I use the AllowPublish property to enable/disable command buttons. When enabled and clicked, I call a service agent which exposes a PublishDocument method accepting the document id. The agent passes the call onto the service application's DocumentService method of the same name.
To eliminate duplicate code, share business logic, validation rules et al, I have placed the domain objects in a separate assembly is shared by both the client application and service application. This means that the client application now has access to the Publish method of my Document class even though it is only relevant and should only ever be used by my service application. This is making me reconsider the entire approach I am taking.
While I understand the use of DTO's to pass state between the client and server, I am using .NET 3.5 and as far as I am aware, sharing the assembly is the only way to share the business and validation rules with the client application. I have some ideas what other directions I can go but was hoping to get some suggestions before embarking down a new path.
On another note, my current implementation for the client takes what I consider to be a round-about approach to authorization that may just be an indicator that a different model would be better. Much like I have an AuthorizationService in my server-side service application that the DocumentService uses to perform authorization, I have a similar agent that my client code uses. This means that I need another layer of indirection in my开发者_运维问答 client code to support authorization, perhaps a Controller or ViewModel. Which is fine if the use case is a valid one.
EDIT
I may need to clarify that the AllowPublish property is dynamic when the Document is being edited. When first retrieved, it may be false but will become true as the business rules are satisfied. Having the business rules running in the client app allows us to provide a richer user experience.
For the sake of closure and anyone that comes across this post in the future, I thought I'd share what I ended up with while giving credit to Iulian for helping steer me in the right direction.
Simply put, I realized (with Iulian's help) that I really did have two different use-cases between the client app and the server-side service app. As a result, I bit my lip and created separate domain models for each.
Part of my thought process was to separate the applications themselves logically. While the client application can't run without the service application, I adjusted my train of thought to see this relationship more as the data access layer than the application and domain layers. At the same time, I shifted my view on the server-side to see the service interface as the presentation layer for that application. As a result, having different domain objects/layers made perfect sense.
Unfortunately this does come with a trade-off. One that I hope to minimize as we advance the technology forward to make use of RIA services, perhaps, which will allow us to pass Data Annotations from server to client. For now, however, I am using simple DTO objects to pass state information between the applications and a RESTful service interface that provides the API into core domain logic.
Using the example I originally cited, I have the following setup:
When the user clicks the "Publish" button in the UI, the client application calls the Publish method on my service agent class. The service agent handles communication with the server-side service. In this case, the DocumentService exposes a Publish method which accepts the ID of the Document to publish (as well as user info, etc.)
The DocumentService retrieves the Document domain object from the DocumentRepository and calls the Publish method on the object which updates the internal state of the Document. The service then calls the Update method on the DocumentRepository, passing in the updated Document object, and the changes are persisted to the database.
The trade-off is that I need to have the logic/rules that determine if and when a Document may be published on both the client and the server (because we can't assume the request is always valid). Again, treating the solution as two separate applications with their own set of use cases helped make this more reasonable (in my mind). As you can see, I don't need a Publish method in the client version of Document but I do need change tracking for a rich user experience. I don't need the same type of change tracking on the server because there is no UI refreshing as the object is changed. In the latter case, change tracking is implemented by the ORM to make persistance more optimal.
So, bottom-line for me is that separating the solution into different applications allowed me to isolate the use cases and draw out the proper objects and relationships required for each application to satisfy its purpose. And now I have a solution that also supports multiple clients because I've decoupled the client from the server in a way that allows me to plug-in new clients without requiring changes to the service application or the existing client applications.
HTH
You should not put your domain model objects in the client. Having them used in the client directly will limit your ability to evolve the domain in future iterations and when doing DDD the ability to evolve your domain when you get deeper insights from the domain experts is vital.
I don't know if this is possible in your case, but maybe you can factor out the business rules as some strategy objects, which will only have very specific behavior that can be used both in the domain model and in the client. This might be ok if your goal is to avoid logic duplication AND if the behavior you need is exactly the same - which might not be the case. In your client you might need some additional steps for the validation which might be different from the steps you need in the Domain Model.
Probably the best solution is to use a MVC or MVVM pattern when you can have the client validation in a ViewModel, if possible based on some shared rules.
I guest the main idea is don't couple concepts for the sake of DRY. As usual Udi Dahan as an article on this: The Fallacy Of ReUse
Consider using the InternalsVisibleTo attribute.
精彩评论