Skip to content
tanukus edited this page May 18, 2020 · 5 revisions

The following Design Patterns will help you continue implementing use cases in a consistent way.

Controller

Controllers receive Requests, build the Input message then call the Use Case, you should notice that the controller do not build the Response, instead this responsibility is delegated to the presenter object.

public sealed class CustomersController : Controller
{
    // code omitted to simplify

    public async Task<IActionResult> Post([FromBody][Required] RegisterRequest request)
    {
        await _registerUseCase.Execute(new RegisterInput(
            new SSN(request.SSN),
            new Name(request.Name),
            new PositiveAmount(request.InitialAmount)));

        return _presenter.ViewModel;
    }
}

ViewModel

ViewModels are data transfer objects, they will be rendered by the MVC framework so we need to follow the framework guidelines. I suggest that you add comments describing each property and the [Required] attribute so swagger generators could know the properties that are not nullable. My personal preference is to avoid setters here because you have total control of response object instantiation, so implement the constructor.

/// <summary>
/// The response for Registration
/// </summary>
public sealed class RegisterResponse
{
    /// <summary>
    /// Customer ID
    /// </summary>
    [Required]
    public Guid CustomerId { get; }

    /// <summary>
    /// SSN
    /// </summary>
    [Required]
    public string SSN { get; }

    /// <summary>
    /// Name
    /// </summary>
    [Required]
    public string Name { get; }

    /// <summary>
    /// Accounts
    /// </summary>
    [Required]
    public List<AccountDetailsModel> Accounts { get; }

    public RegisterResponse(
        Guid customerId,
        string ssn,
        string name,
        List<AccountDetailsModel> accounts)
    {
        CustomerId = customerId;
        SSN = ssn;
        Name = name;
        Accounts = accounts;
    }
}

Presenter

Presenters are called by the application Use Cases and build the Response objects.

public sealed class RegisterPresenter : IOutputPort
{
    public IActionResult ViewModel { get; private set; }

    public void Error(string message)
    {
        var problemDetails = new ProblemDetails()
        {
            Title = "An error occurred",
            Detail = message
        };

        ViewModel = new BadRequestObjectResult(problemDetails);
    }

    public void Standard(RegisterOutput output)
    {
        /// long object creation omitted

        ViewModel = new CreatedAtRouteResult("GetCustomer",
            new
            {
                customerId = model.CustomerId
            },
            model);
    }
}

It is important to understand that from the Application perspective the use cases see an OutputPort with custom methods to call dependent on the message, and from the Web Api perspective the Controller only see the ViewModel property.

Standard Output

The output port for the use case regular behavior.

Error Output

Called when an blocking errors happens.

Alternative Output

Called when an blocking errors happens.

Unit of Work

public interface IUnitOfWork
{
    Task<int> Save();
}
public sealed class UnitOfWork : IUnitOfWork, IDisposable
{
    private MangaContext context;

    public UnitOfWork(MangaContext context)
    {
        this.context = context;
    }

    public async Task<int> Save()
    {
        int affectedRows = await context.SaveChangesAsync();
        return affectedRows;
    }

    private bool disposed = false;

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

First-Class Collections

public sealed class CreditsCollection
{
    private readonly IList<ICredit> _credits;

    public CreditsCollection()
    {
        _credits = new List<ICredit>();
    }

    public void Add<T>(IEnumerable<T> credits)
        where T : ICredit
    {
        foreach (var credit in credits)
            Add(credit);
    }

    public void Add(ICredit credit)
    {
        _credits.Add(credit);
    }

    public IReadOnlyCollection<ICredit> GetTransactions()
    {
        var transactions = new ReadOnlyCollection<ICredit>(_credits);
        return transactions;
    }

    public PositiveAmount GetTotal()
    {
        PositiveAmount total = new PositiveAmount(0);

        foreach (ICredit credit in _credits)
        {
            total = credit.Sum(total);
        }

        return total;
    }
}

Factory

public interface IEntityFactory
{
    ICustomer NewCustomer(SSN ssn, Name name);
    IAccount NewAccount(ICustomer customer);
    ICredit NewCredit(IAccount account, PositiveAmount amountToDeposit);
    IDebit NewDebit(IAccount account, PositiveAmount amountToWithdraw);
}
public sealed class EntityFactory : IEntityFactory
{
    public IAccount NewAccount(ICustomer customer)
    {
        var account = new Account(customer);
        return account;
    }

    public ICredit NewCredit(IAccount account, PositiveAmount amountToDeposit)
    {
        var credit = new Credit(account, amountToDeposit);
        return credit;
    }

    public ICustomer NewCustomer(SSN ssn, Name name)
    {
        var customer = new Customer(ssn, name);
        return customer;
    }

    public IDebit NewDebit(IAccount account, PositiveAmount amountToWithdraw)
    {
        var debit = new Debit(account, amountToWithdraw);
        return debit;
    }
}

Component

Clone this wiki locally