Let entity A
relates to entities B
and C
both as 1:1..*
At the same time B
contains only part of C
's key, but together with A
's key it is possible to relate B
to C
.
Translation to human language:
Accounting Center (A) consists of Branches (C), and a stream of Payments (B) comes to the system from Accounting Center and contains BranchId which is unique only within a given Accounting Center. Wee need to check the correctness of provided BranchId and tie the Payment to the Branch.)
We process the collection of B
s:
class BsProcessor
{
private BProcessor _processor;
void ProcessBs(IEnumerable bs)
{
for (var b in bs)
{
_processor.Process(b);
}
}
}
One day requirements change, and now BProcessor
needs to make a decision based on corresponding C
.
From the view of performance it's better to get in advance all C
s for the A
s which B
s point to, and then pass this data to BProcessor via changing Process
method signature.
class BsProcessor
{
private BProcessor _processor;
private CProvider _provider;
void ProcessBs(IEnumerable bs)
{
var aKeys = bs.Select(b => b.aKey);
var cs = _provider.GetCsForAs(aKeys).ToDictionary( c => c.aKey);
for (var b in bs)
{
var cCandidates = cs[b.aKey];
_processor.Process(b, cCandidates);
}
}
}
BProcessor
then tries to find the matching C
.
This is a straightforward fast to code and simple working solution...
To be honest I don't like it wholeheartedly. I think it violates SRP.
There is no any other reason but performance for the BsProcessor
to be aware of Cs
.
The logic of finding C
candidates and matches belongs neither to BsProcessor
nor to BProcessor
, but a dedicated service CMatcher
class CMatcher
{
private CProvider _provider;
private IEnumerable<aKey> _aKeys;
public CMatcher(CProvider provider, IEnumerable<aKey> aKeys)
{
...
}
public C Match(aKey akey, cPartialKeyFromB partialKey)
{
}
}
This service should be injected and used by BProcessor
. As this service is contextual and requires collection of aKeys we need to switch to the factory:
class BsProcessor
{
private BProcessorFactory _factory;
void ProcessBs(IEnumerable bs)
{
var aKeys = b.Select(b => b.aKey);
var processor = _factory.Create(aKeys);
for (var b in bs)
{
processor.Process(b);
}
}
}
I think this solution is a bit more complex to code, but easier to test.
The only problem I have is that BProcessor
contains more than one method, and those do not require to match C
to B
, hence it is not good to use constructor injection, whereas method injection makes BsProcessor
know about CMatcher
what is somewhat similar to initial solution.
Here it starts looking like BProcessor
begs for being divided into separate classes, and I ask myself whether SRP is worth so much refactoring and where second approach is indeed better than the first one?