|  | Domain Driven Design and SaveAll() |  | |
| | | Daniel C |  |
| Posted: Wed Sep 03, 2008 12:42 am Post subject: Domain Driven Design and SaveAll() |  |
| |  | |
In our domain driven design, we allow clients to consume a service that includes a "Save Step" method (public Step SaveStep(Step userStep). This works great for the consumers of the web service (easy call, just send back entire step object) and for persisting the step data to the data store (just send the step to the repos).
It gets a little more complicated when we introduce the idea that SaveStep should, in addition to persisting data, trigger a specific event that is captured by an outside process, such as a workflow engine. A Step in this case is somewhat complex; a collection of users, collection of documents, and permissions per user on the documents. We need to trigger events such as user added, removed, permission set added/removed, etc. Is there a best practice/pattern/ example to approaching this; basically, comparing the passed in step to the persisted step and generating what has changed?
I could obviously write a very specific algo to compare the saved and new steps, with hard-coded references to each component in the Step...but this would be ugly and not very adaptable to changes in the Step in the future. |
| |
| | | H. S. Lahman |  |
| Posted: Wed Sep 03, 2008 1:31 pm Post subject: Re: Domain Driven Design and SaveAll() |  |
| |  | |
Responding to Daniel C...
| Quote: | In our domain driven design, we allow clients to consume a service that includes a "Save Step" method (public Step SaveStep(Step userStep). This works great for the consumers of the web service (easy call, just send back entire step object) and for persisting the step data to the data store (just send the step to the repos).
|
<Hot Button> Passing objects around like this does save some keystrokes in the short term but it tends to lead to fragile applications that are difficult to maintain. Passing object references is the worst form of coupling because the receiver can do virtually anything with the object and some of those things may be quite unexpected from the sender's viewpoint. Another maintenance problem is that the sender implementation of the objects may change and that may require changes to the receiver.
In effect, passing a problem space object effectively trashes implementation hiding and bleeds cohesion across subsystem or layer boundaries because such an object exposes the implementation of the sender. (Computing space objects like String that are pure data holders aren't a problem.) If one uses a general purpose interoperability infrastructure to save even more keystrokes, one will also pay a substantial performance penalty. All in all it is not a real good idea, especially across distributed boundaries.
If you limit your interfaces to pure messages (i.e., {message id, by-value data packet}) like events, the interface will tend to be finer grained and will require encode/decode on each side but the application will be faster, more robust, and far more maintainable. That's because the encode/decode decouples the contexts so that the only thing shared between them is the definition of the message. That allows each context to interpret the message in a unique fashion for its local problem space. </Hot Button>
| Quote: | It gets a little more complicated when we introduce the idea that SaveStep should, in addition to persisting data, trigger a specific event that is captured by an outside process, such as a workflow engine. A Step in this case is somewhat complex; a collection of users, collection of documents, and permissions per user on the documents. We need to trigger events such as user added, removed, permission set added/removed, etc. Is there a best practice/pattern/ example to approaching this; basically, comparing the passed in step to the persisted step and generating what has changed?
|
I am missing something here because I don't see the problem. B-(
An event is an announcement of some change in solution state. The event is triggered by that change so it must be generated where the change occurs. The event needs to be consumed by somebody who cares that the state of the solution has changed in a particular way so the event is addressed to whoever that is.
Connecting those dots is a pretty straight forward resolution of flow of control. (In an event-based system one can rigorously, albeit sometimes tediously, resolve flow of control with DbC by matching method post conditions to the precondition for executing the receiver method.)
Once one has connected the dots, it should be obvious what information about the source context needs to be supplied with the event for the receiver. That information might be complex because of multiple users, documents, etc. but that just means the message data packet gets bigger.
[Apropos of the point above: the more information needed, the more valuable the encode/decode decoupling is. If the information needs to be extracted from and loaded to multiple objects on each side, hiding those objects is critical. That's because they are more likely to change than the information needed across a distributed boundary. For example, the service still needs the same information but the more objects there are on the source side, the more likely it will be for the source to reorganize its management of the information with object changes in its implementation when requirements for just its subject matter change. But if the objects are passed directly to the receiver, that will trigger changes in the receiver as well.]
| Quote: | I could obviously write a very specific algo to compare the saved and new steps, with hard-coded references to each component in the Step...but this would be ugly and not very adaptable to changes in the Step in the future.
|
This is just an example of what goes wrong when one passes problem space objects around, especially across subsystem boundaries. If each subsystem manages it own knowledge and behavior responsibilities in isolation, then all one needs to define is a client/service protocol for messages exchanged between the subsystems. Then the objects implementing each subsystem are fully hidden and can be modified without touching the protocol or the other subsystems. Nor does one subsystem need to understand the implementation of another subsystem to do its thing.
-- There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@pathfindermda.com Pathfinder Solutions LINK blog: LINK "Model-Based Translation: The Next Step in Agile Development". Email info@pathfindermda.com for your copy. Pathfinder is hiring: LINK (888)OOA-PATH |
| |
|
|