How do you expose an aggregate member for the purpose of presentation, while at the same time preventing that member from getting modified directly? So, I need something like read-only access to that member for the purpose of showing it on UI, For example:
class A {
B b;
void doSomething() {
b.update();
}
}
class B {
String getTitle() {
return title;
}
Items getItems() {
return items;
}
void update() {
...
}
}
interface SomeView {
void show(Items items);
}
The quick-and-dirty solution would be to add a method A::getB()
and call someView.show(b.getItems())
but then the returned B
instance could be modified outside of A
(the B::update()
could be called outside A
).
I am thinking of several solutions but not quite sure which are best or if there is already a common practice to do so.
One of the solution that I have in mind is having a read-only version of B
that is returned by A
.
class A {
ReadOnlyB getReadOnlyB() {
return new ReadOnlyB(b);
}
}
class ReadOnlyB {
B b;
ReadOnlyB(B b) {
this.b = b;
}
String getTitle() {
return b.getTitle();
}
Items getItems() {
return b.getItems();
}
}
You could use read-only interfaces for B
. Internally, in A
class, you use the class B
but to the outside (to the clients) you give a read-only interface of B
. Here is a sample:
class A
{
private B b;
void doSomething() {
//internally you use this.b
b.update();
}
//please name this method according to its purpose
ReadOnlyB getB()
{
return b;
}
}
class B implements ReadOnlyB
{
String getTitle() {
return title;
}
Items getItems() {
return items;
}
void update() {
...
}
}
//please name this interface according to its purpose!
interface ReadOnlyB
{
Items getItems();
}
interface SomeView
{
void show(Items items);
}
However, this would break the Law of Demeter. It would be better to return the Items
from A
, like this:
class A
{
private B b;
void doSomething() {
b.update();
}
Items getItems()
{
return b.getItems();
}
}
Don't expose domain objects to the presentation layer, map them to readonly presentation models (from your comment, that's already what you identified as the next thing to do).
You can have a look at CQRS for a stricter, more opinionated take on read models.
Anyway, even for entity of the same layer, the A::getB()
would still
not be a good thing.
If you stay inside the domain layer, it's actually legit. Entities can have references to other entities - including transient references to entities from other aggregates. However, if A and B belong to different aggregates, you must be aware that it violates the Aggregate as consistency boundary approach since you're now prone to modifying multiple aggregates in the same transaction. You're also making your life harder by increasing the chances of concurrent access to B.
You could also let the objects present themselves, in which case there would be absolutely no need to expose hidden structures. This would be more in line with Object-Orientation, Law-of-Demeter, etc. Like this:
public class A {
private B b;
...
public void paint(Graphics g) {
...
b.paint(g);
...
}
}
Or, using a Wicket-type presentation to Web:
public class A {
private B b;
...
public Component display() {
return new APanel(..., b.display(), ...);
}
}