Reputation: 639
At first let me describe a lit bit domain. We have a website, where a client can place an order. To place order, the client must provide some data. This process is divided into steps. On each step, the client provides only part of the data. When the client completes the last step - all data the needed for order is ready.
So we have an entity StepsProgression. Inside there is an array of value objects "Step". They don't store anything, so they are simple and perfectly suited to be value objects. But in order to persist user data across all steps, inside StepsProgression there is also an object StepsData.
And here comes the trouble. StepsData will have setters, to set user data. So it must be an Entity. But from the domain perspective it is not an Entity. It is a Value object, because I don't care about its identity. I can replace it with object with the same data and it is OK.
What can you recommend in this situation?
EDIT 1
About Domain again
We indeed have a booking system. And we asked domain experts and we indeed have different steps (or stages) to fill some specific data in order to book order item for user. So the concept of Step's and StepsProgressions is okay. It is not coupled with UI. On UI side, for example, we filling data for two steps simultaneously.
Upvotes: 1
Views: 328
Reputation: 639
So finally I choose to make StepsData
as Value Object. It gives me a benefit of write protection outside of the StepsProgression
.
I can pass StepsData
outside of StepsProgression
for read only with no worries, because if someone outside will call a setter on StepsData
, a new object will be returned. And outside of StepsProgression
you can't rewrite original StepsData
. So even if someone calls setter, original StepsData
in StepsProgression
is still unchanged.
Upvotes: 0
Reputation: 3880
First re-check the domain for StepsProgression: If we ask a domain expert about our process' steps, I think we get an acceptable answer. Then we can say this is part of our domain.
Next, as you say is this entity or VO? Yes it looks like value object, and actually is a value object as you dont care about the identity of the whole data set, it does not have a unique meaning (if I did not understand domaing wrong).
Assuming you have a UI like a form wizard and every step's inputs are different, I would Implement like this:
public class StepsProgression
{
private readonly string _userId;
public Step1 step1 { get; set; }
public Step2 step2 { get; set; }
public Step3 step3 { get; set; }
public StepsProgression(string userId)
{
_userId = userId;
}
}
// Immutable Step Object
public class Step1
{
public string input1 { get; }
public string input2 { get; }
public string input3 { get; }
}
You instantiate StepsProgression in the first step, so I assume you only know whose steps are they. So constructor only sets user ID. Then when user fills step1 inputs and clicks Next then you create a new (immutable) Step object and set it to step1. If user clicks Back, changes anything from Step1 and clicks Next again, then you again create a new Step1 object and assign it to StepsProgression.
You can think more generic implementation (Step) but as the inputs are all different between steps, this explicit way gives you coding convention.
Edited Implementation: I understand that StepsProgressions contains a dynamic set of steps which you determine just before creating StepsProgression. Then assuming you know stepTypes beforehand I would change my implementation as follows:
public class StepsProgression
{
private readonly string _userId;
private readonly IStepProgressionBuilder _builder = new StepProgressionBuilder();
public IEnumerable<IStep> Steps { get; set; }
public StepsProgression(string userId, IEnumerable<string> stepTypes)
{
_userId = userId;
foreach (var step in stepTypes) // foreach smells here but you got the point
{
_builder.AddConcreteStep(step) // step = "Step1" for instance.
}
Steps = _builder.Build();
}
}
// Immutable Step Object
public class Step1 : IStep
{
public string input1 { get; }
public string input2 { get; }
public string input3 { get; }
}
// Builder Pattern with Fluent Methods
public interface IStepProgressionBuilder
{
// stepType = "step1" for instance. You have concrete Step1 class, so you return it.
// Start with a switch, then you refactor. (May involve some reflection)
void AddConcreteStep(string stepType);
IEnumerable<IStep> Build();
}
Again you can use one generic Step class, but then your builder should shomehow build a Step by taking each data that Step should contain. This then becomes a codesmell if your steps may have more than 3 properties. Thats why I prefer having concrete Step classes around so that I can build it as a whole dataset.
Upvotes: 0
Reputation: 41
From my rare knowledge by reading your question / description it seems to me, that you are building some kind of online shop, booking system or something similar.
This assumed please analyse your domain carefully. Ask yourself the question, what your domain really is and if StepsProgression
, Step
and StepData
are really “Domain concerns” of such an ordering system…?
Personally I have the feeling that these are simply abstractions of the UI workflow and don’t reflect any domain specific concepts at all – a pure technical perspective of the application.
In this case they would be neither Entity nor Value Object because they aren't even part of the Domain Model.
I would suggest going back to the whiteboard and first start modelling a Domain Model consisting of only domain specific objects (+ many more), without having the UI or use cases in mind too much:
- Order (Entity)
- OrderNumber (Value Object)
- Customer (Entity)
- PaymentType (Entity)
- OrderTotal (Value Object)
- …
By combining them to well suited Aggregates (transaction boundaries), persisting them with Repositories and processing them with Domain Services you should be able to create a “rich” Domain Model.
The use cases of your application (i.e. collecting and persisting the order relevant data from the user in the right chunks) will then be orchestrated by Applications Services that make use of your existing Domain Model.
Afterwards some smaller refactorings to the Domain Model could be required but keep in mind, that UI, application or infrastructure specific concerns should follow the Domain Model and not “leak” into it.
Maybe I got your question completely wrong: in this case sorry for the inconvenience. But as I see it a general reconsidering / questioning of the overall Domain and the regarding model seems to be helpful.
Upvotes: 2
Reputation: 7285
A value object can have getters and setters. Looks like in your case StepsData
describes the entity's(StepsProgression) state which makes it a value object candidate. You can have a value object property in a value object itself. Value object being self-contained makes it fundamentally easier to work with. For a DDD purist, value objects are immutable, side effect free and easily testable.
Upvotes: 1