I have a domain layer, which my application architecture holds "sacred"; that is to say, the domain layer has no references to either data storage, or presentation concerns (vice-versa is allowed). I would like to keep it this way!
Everything was going quite well until I came to one particular requirement. Given:
public class A
{
public string IdA { get; private set; }
public void DoWork()
}
public class B
{
public string IdB { get; private set; }
public string IdA { get; private set; }
public Invaidate()
}
Under certain uncommon conditions, which A decides, when DoWork is called all associated B must be invalidated. The problem, this invalidation requires persistence. Note: this edge case is the only time A would need to be concerned with its associated Bs. The requirement for this invalidation would not be obvious to a consumer of A.
I have come up with, and implemented, a solution (1), as well as thought of other solutions. However, I am not quite happy with it, or any of the others, and would like input.
Solution 1: A Invalidates Bs, Calls Delegate For Persistence
Advantage: Transparent to consumer, Easy to consume
Disadvantage: Strange and inconsistent save behavior, no other class in the domain layer saves when a method is called
class A
{
private Action<B> SaveB; // Populated by ARepository when instance is retrieved, delegate points to sister repo BRepository
public List<B> Bees { get; private set; }
public DoWork()
{
// if edgeCase:
foreach (B b in Bees)
{
b.Invalidate();
SaveB(b);
}
}
}
Solution 2: A Invalidates Bs, A's Repository Saves Associated Bs
Advantage: Transparent to consumer, Easy to consume, Consistent Domain Layer
Disadvantage: Inconsistent persistent layer, Monolithic Persistence Antipattern*
*Perhaps the wrong anti-pattern name?
class A
{
public List<B> Bees { get; private set; }
public DoWork()
{
// if edgeCase:
foreach (B b in Bees)
b.Invalidate();
}
}
//...
class ARepository
{
Update(A a)
{
Sql.SaveA(a);
bRepository.Update(a.Bees);
}
}
Solution 3: Trigger Event On Edge Case
Advantage: No inconsistencies in domain or persistence layer behavior, No cross entity concerns
Disadvantage: Hard to consume, Exposes logic to consumer, who is supposed to be responsible for wiring up the event?!
class A
{
public Event SaveBs/EdgeCaseHappened;
public List<B> Bees { get; private set; }
public DoWork()
{
// if edgeCase:
foreach (B b in Bees)
b.Invalidate();
SaveBs.Fire();
// or just...
EdgeCaseHappened.Fire()
}
}
While I have already implemented 1, none of these three solutions seem quite right to me, is there something here that I am missing? Have I made a faulty assumption somewhere?
What is the best course of action for me too keep cross entity concerns like this maintainable and usable, while strictly isolating my business logic?
Note: this is not a DDD project