1

What is the best way to actually implement service classes when following domain driven design?

For example say I want to have an AccountService which can be used to transfer funds from one account to another? Which of the following, if any, would be the best way to implement this?

public class AccountService1
{
    private IAccountRepository _accountRepo;

    public AccountService1(IAccountRepository accountRepo)
    {
        _accountRepo = accountRepo;
    }

    public void TransferFunds(double ammount, int sourceAccountNumber, int targetAccountNumber)
    {
        //FUNDS TRANSFER CODE
    }
}

public class AccountService2
{
    public void TransferFunds(double ammount, Account sourceAccount, Account targetAccount)
    {
        //FUNDS TRANSFER CODE
    }
}


public static class AccountService3
{
    public static void TransferFunds(double amount, Account sourceAccount, Account targetAccount)
    {
        //FUNDS TRANSFER CODE
    }
}

public static class AccountService4
{
    public static void TransferFunds(double amount, int sourceAccountNumber, int targetAccountNumber, IAccountRepository repository)
    {
        //FUNDS TRANSFER CODE
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Eric Anastas
  • 21,675
  • 38
  • 142
  • 236

3 Answers3

3

Every way you listed leads down the road to an anemic domain.

Is there some reason it can't be an instance method on your account entity?

public class Account {
    public void Transfer(decimal amount, Account recipient);
}
quentin-starin
  • 26,121
  • 7
  • 68
  • 86
2

It's not clear if your examples are Application Services or Domain services. Example 1 is what the Application service should look like, and example 2 is what a Domain service might look like. Put together, it would look something like this:

public class AccountApplicationService
{
    private IAccountRepository _accountRepo;

    public AccountApplicationService(IAccountRepository accountRepo)
    {
        _accountRepo = accountRepo;
    }

    public void TransferFunds(double ammount, int sourceAccountNumber, int targetAccountNumber)
    {
        Account sourceAccount = _accountRepo.GetById(sourceAccountNumber);
        Account targetAccount = _accountRepo.GetById(targeteAccountNumber);

        IAccountDomainService accDomService = new AccountDomainService();

        accDomService.TransferFunds(ammount, sourceAccount, targetAccount);

        using(Transaction tran = _accountRepo.BeginTransaction()) //note: pseudo code.
        {
             _accountRepo.Save(sourceAccount);
             _accountRepo.Save(targetAccount);
             tran.Commit();
        }
    }
}
Paul T Davies
  • 2,527
  • 2
  • 22
  • 39
0

Your first implementation is the best option. However, you should also implement a Transfer method on the Account class itself which accepts an amount and a target Account instance as well. The role of the service in this case is to load Account instances using the repository, save them, unit of work management, as well as providing access to any external services that may be needed. The role of the Transfer method on the Account class is to protect the integrity of the data within the Account class itself and ensure consistency, throw exceptions etc.

class Account {

  public string State { get; private set; } 

  public decimal Total { get; private set; } 

  public void Transfer(decimal amount, Account dest) {
     if (amount > this.Total) throw Exception();

     if (this.State == "Closed") throw Exception();

     this.Total -= amount;
     dest.Total += amount;

  }
}

So the Account class defines what operations are possible and the service layer is what operates upon the domain layer and is thus concerned with all operational responsibilities.

eulerfx
  • 36,769
  • 7
  • 61
  • 83