2
\$\begingroup\$

At now I have such code, creating or updating some entity by dto items.

public void Upsert(List<Object> items)
{
    var existings = _db.Get().Where().ToList();
    foreach (var item in items)
    {
        var existing = existings.FirstOrDefault(x => x.Id == item.SomeId);
        if (existing == null)
        {
            if (item.Property != 645)
            {
                throw new EntityNotFoundException();
            }

            // create new
        }
        else
        {
            // update existing
        }
    }
    _db.Save();
}

I want to move if (item.Property logic out of this layer. I cannot add some method to Object, it is simply dto. I also cannot subclass UpsertService.

What I can do is to pass Action<Object> checkWhenNotFound to Upsert method. Disadvantage - I need to write all those mocking It.IsAny<Action<Object, NotOnlyObjectInReal, AndMaybeMore>> in tests.

Other way is to inject checker inside Upsert caller ctor:

interface IUpsertService {
    void Upsert(List<Object> items);
    void SetChecker(Action<Object>);
}
public Owner(IUpsertService service)
{
    _service = service;
    service.SetChecker(CheckWhenNotFound)
}
private CheckWhenNotFound(Object item)
{
    if (item.Property != 645)
    {
        throw new EntityNotFoundException();
    }
}

Is there commmon solution for such problem?

\$\endgroup\$
  • \$\begingroup\$ Do I get it right, the IUpsertService is your service that cannot be changed? \$\endgroup\$ – t3chb0t Nov 1 '16 at 10:45
  • \$\begingroup\$ It is my service, but IUpsertService is only one of its interfaces, so subclassing is no way. \$\endgroup\$ – smg Nov 1 '16 at 11:08
  • \$\begingroup\$ Can you change the service at all or is it sealed/locked/closed for any modifications? \$\endgroup\$ – t3chb0t Nov 1 '16 at 11:11
  • \$\begingroup\$ I can change it, in my second snippet you see example of modification (added SetChecker) \$\endgroup\$ – smg Nov 1 '16 at 11:20
  • \$\begingroup\$ Why offtpic? I think is is "fabricating a related realistic program that looks plausibly useful". \$\endgroup\$ – smg Nov 2 '16 at 14:42
4
\$\begingroup\$

Can't add a comment, so I'm going to try to answer the question.

If I'm understanding the question correctly, you don't want the responsibility of validation to be in the Upsert method. If that is the case then you can invert the control to another class by passing a 'Validator'. Which I think is what you are alluding to in the second part of the question but I would recommend the following implementation.

In a nutshell what you do is create a class which is responsible for validation which is injected into the main class. This makes this testable as well.

private readonly IValidator _validator;

public Constructor(IValidator validator)
{
  _validator = validator;
}

public void Upsert(List<Object> items)
{
    var existings = _db.Get().Where().ToList();
    foreach (var item in items)
    {
        var existing = existings.FirstOrDefault(x => x.Id == item.SomeId);
        if (existing == null)
        {
            _validator.ValidateEntity(existing);
        }
        else
        {
            // update existing
        }
    }
    _db.Save();
}

Example Implementation of the IValidator interface would be

public Validator : IValidator
{
  void ValidateEntity(object existing)
  {
    if (item.Property != 645)
    {
      throw new EntityNotFoundException();
    }
  }
}

You could take this further and use generics and allow the Validator class to decide what the object is and what needs validating.

\$\endgroup\$
  • 1
    \$\begingroup\$ With composition you could even combine multiple validators into one and do more the one validation at a time. \$\endgroup\$ – t3chb0t Nov 1 '16 at 19:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.