Today I want to present a different point of view on C# applications design. If you are programming in that language most of the time, it’s probably not what you’re used to see. However I found it interesting, because it allows to achieve a few important goals:

  • Decompose various, complex interfaces into few simpler, well build components.
  • Seriously reduce risk of common OO anti-patterns in your application i.e. God Object.
  • Make your code shorter and methods simpler.
  • Replace a lot of external assembly dependencies in your code with some simple design patterns.

If you’re ready, prepare to step down to the Wonderland. Let the Catepillar be our guide.

The basics

Step 1 – define an interface

Lets start with twisting our mindsets a little bit. Take the following example:

public interface IAuthService
{
   Task<User> SignUp(string email, string password);
   Task<User> SignIn(string email, string password);
   Task ResetPassword(string email);
}

This is our new trendy-looking, non-blocking service interface. What’s also important here, it follows a Single Responsibility Principle.

Step 2 – separate responsibilities

Since SRP has no well-defined bounds and it is a matter of a personal taste, we could stretch it a little further:

public interface ISignInService
{
    Task<User> Handle(string email, string password);
}

public interface ISingUpService
{
    Task<User> Handle(string email, string password);
}

public interface IResetPasswordService
{
    Task Handle(string email);
}

Now instead of one service responsible for three possible operations, we have a three atomic services, each one having only one operation to take care off. If you’re thinking, it’s overly complicated, you’re probably right. But right now we’re still in Objective Oriented world and the Wonderland awaits ahead.

Step 3 – more simplification

Now, when we have a three single-member services, take a look into the mirror. If you do, you’ll notice an important characteristic – interface with single method is almost indistinguishable from a delegate. It looks better now, see?:

public delegate Task<User> SignUpService(string email, string password);
public delegate Task<User> SignInService(string email, string password);
public delegate Task ResetPasswordService(string email);

At this point you may want to turn back. This design is problematic. For example, it isn’t as easily composable with Dependency Injection frameworks as interfaces. Don’t worry. We will cover this up later. Now step in.

Step 4 – final generalization

Our service signatures are now almost ready. But to reach behind the mirror, we must apply two more rules:

  • Each service takes no more than one parameter.
  • Each service should always return a value.

Why does it matter? If each service will satisfy one input / one output rule, we may compose them together with ease.

public delegate Task<User> SignUpService(SignUpRequest request);
public delegate Task<User> SignInService(SignInRequest request);
public delegate Task<object> ResetPasswordService(string email);

Now, all of our delegates could be abstracted away to a single generic definition:

public delegate Task<TRep> Service<in TReq, TRep>(TReq request);

where:

  • TReq is type of request object passed to service.
  • TRep is type of service reply.

What we end up with is a highly abstracted – in Wonderland things looks different – and universal service signature.

Dependency Injection vs partial application

Walking down the object oriented road, often you could passed a view similar to this one:

public class TransportationService
{
    private readonly ILocalizationService _localizationService;
    private readonly IOrdersRepository _ordersRepository;
    private readonly ICustomersRepository _customersRepository;
    private readonly IConfigurationProvider _configuration;

    public TransportationService (
        ILocalizationService localizationService,
        IOrdersRepository ordersRepository,
        ICustomersRepository customersRepository,
        IConfigurationProvider configuration)
    {
        _localizationService = localizationService;
        _ordersRepository = ordersRepository;
        _customersRepository = customersRepository;
        _configuration = configuration;
    }

    public async Task<TransportationDetails> PrepareTransportation(int orderId)
    {
        ...
    }
}

But now when you’re on the other side of the mirror, it looks more like:

public static TransportationServices
{
    public static Service<int, TransportationDetails> PrepareTransportationService (
        ILocalizationService localizationService,
        IOrdersRepository ordersRepository,
        ICustomersRepository customersRepository,
        IConfigurationProvider configuration)
        {
            return async orderId => { ... };
        }
}

Here we simply return an asynchronous lambda. And because it’s nested inside, it can use all of the provided parameters directly in it’s scope.

Of course, there is still a matter of lifetime scopes. In case of singleton scopes, we simply may pass shared instance directly. But when more universal lifetimes are required, we can slide down the road along with the delegates to reach even higher abstractions – it’s twisted, but we’re in Wonderland, remember?

public static Service<LocalizationRequest, LocalizationReply> LocalizationService() { ... }

public static Service<MyRequest, MyReply> MyService(Func<Service<LocalizationRequest, LocalizationReply>> localizationServiceProvider) { ... }

// transient scope
var myService = MyService(() => LocalizationService());
// or even shorter
var myService2 = MyService(LocalizationService);

// singleton scope
var localizator = LocalizationService();
var myService = MyService(() => localizator);
var myService2 = MyService(() => localizator);

There is a one simple but powerful idea visible on the example above – an input parameter type has been changed from interface to another delegate. Now it’s delegates all the way down. This way we may start from the most atomic services and combine them into more complex ones without limitations.

Instead of complex, reflection-based DI frameworks we have one simple universal abstraction. You may find this more verbose, but it’s actually simpler, faster and more error-proof solution than any of IoC libraries. You don’t need to learn next DI framework or use StackOverflow guide to travel through the Wonderland.

There are other problems solved automatically:

  • No need to worry about cyclic references.
  • There is no risk, that our DI framework won’t know how to bind parameters to construct object. You’ll never get a runtime errors when walking this way.
  • Your application won’t inject bad interface implementation by mistake.

Repositories and testing

Another popular OO desing pattern is a repository and it’s most corrupted (and misunderstood) version – generic repository.

public interface IGenericRepository<T>
{
    Task<IEnumerable<T>> GetAll();
    Task<T> GetById(int id);
    Task<T> Create(T entity);
    Task<T> Update(T entity);
    Task<bool> Delete(T entity);
}
public interface IUserRepository : IGenericRepository<User> {
    Task<User> GetByEmail(string email);
}

Lets be honest – you’ll probably never use all of those methods at once. If you think it’s good abstraction, it’s not. It isn’t a good SRP example either. After we had stepped into the mirror, we’ve surely taken something from our world with us. So lets take a one of the things we’ve hidden in the pocket – changing user password.

public class UserService
{
    private readonly IUserRepository _userRepository;
    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public async Task<PasswordChanged> ChangePassword(ChangePassword request)
    {
        var user = await _userRepository.FindByEmail(request.Email);
        if(IsValid(user, request.OldPassword))
        {
            user.PasswordHash = ComputePasswordHash(request.NewPassword);
            await userUpdater(user);
            return new PasswordChanged();
        }
        else throw new UnauthorizedException();
    }

    private byte[] ComputePasswordHash(string password) { ... }
    private bool IsValid(User user, string password) { ... }
}

Basically changing password would require only two out of six methods provided by IUserRepository – matching user and saving his/her state after changing. Now smoke the Catepillar’s hookah and take a look again:

public static Service<ChangePassword, PasswordChanged> ChangePasswordService(Func<Service<string, User>> userFinderProvider, Func<Service<User, User>> userUpdaterProvider)
{
    var userFinder = userFinderProvider();
    var userUpdater = userUpdaterProvider();

    return async request => {
        var user = await userFinder(request.Email);
        if (IsValid(user, request.OldPassword))
        {
            user.PasswordHash = ComputePasswordHash(request.NewPassword);
            await userUpdater(user);
            return new PasswordChanged();
        }
        else throw new UnauthorizedException();
    };
}

private static byte[] ComputePasswordHash(string password) { ... }
private static bool IsValid(User user, string password) { ... }

We totally dealt with repository interface, introducing two service providers presented before.

Hint:

You can turn ComputePasswordHash (or even IsValid) into service on it’s own. All hail the modularity!

From testing perspective …

In traditional OO world, to test this feature, you’d probably include some mocking library, mock repository’s interface and check if correct method was called with correct arguments. You may also mock underlying database directly with something like Effort.

In Wonderland mocklibs are quite rare creatures. They are even harder to catch. We must find another way. Can we simply test it without any mocking library? Lets see:

// Arrange:
// if you abstracted ComputePasswordHash earlier, it'll be easier at this point
var testUser = new User(email, passwordHash);
var db = new Dictionary<string, User> { { testUser.Email, testUser } };
Service<string, User> userFinder = async email => {
    User user;
    return db.TryGetValue(email, out user) ? user : null;
};
Service<User, User> userUpdater = async user => user;
    var changePasswordService = ChangePasswordService(() => userFinder, () => userUpdater);

// Act:
await changePasswordService(new ChangePassword(email, oldPassword, newPassword));

// Assert:
Assert.NotEqual(passwordHash, testUser.PasswordHash);

Actually, that’s all. Including mock initialization and result verification. No mocklib was harmed in the making of this test.

Aspect Oriented Programming vs lambda combinators

If you’re still interested in miracles of the Wonderland, we can go further. Next question: how can we bend reflection-based Aspect Oriented Programming to our will? There are possibly many ways, but I’ll focus only on the one.

Just like we used services to replace all of our interfaces, we replace aspects / attributes with filters:

public delegate Task<TOut> Filter<in TIn, TOut, out TReq, TRep>(TIn request, Service<TReq, TRep> service);

where:

  • TIn is filter’s input type.
  • TOut is filter’s output type.

Filter is actually a wrapper around specific service, which may apply additional code before or after it, modify it’s input or output or even decide not to call it at all. In case when filter don’t change service’s in / out parameters, it’s signature is basically equal to public delegate Task<TRep> Filter<TReq, TRep>(TReq request, Service<TReq, TRep> service);.

Now, having those two types defines, lets create some utility methods, which can make our travel easier. Lets start with filter composer.

public static Filter<TIn, TOut, TReq2, TRep2> Then<TIn, TOut, TReq1, TRep1, TReq2, TRep2>(
    this Filter<TIn, TOut, TReq1, TRep1> self,
    Filter<TReq1, TRep1, TReq2, TRep2> next)
{
    return (request, service) => self(request, req => next(req, service));
}

If we omit the method signature, this method is a pretty straightforward one liner which combines two filters together and gives another filter in return. As you can guess, with this trick we can join a whole chains of filters into one piece.

There is a second part of the puzzle missing. We still don’t have a well defined methods to work with both filters and services. This is the one:

public static Service<TReqIn, TRepOut> ApplyTo<TReqIn, TRepOut, TReqOut, TRepIn>(
    this Filter<TReqIn, TRepOut, TReqOut, TRepIn> filter,
    Service<TReqOut, TRepIn> service)
{
    return request => filter(request, service);
}

Just like we were combining filters, now we combine a filter with a service to get service in return.

With this two methods (and basically two lines of code!) we can easily interop between filters and services. How will this weapon examine against aspects?

// logging example
public static Filter<TReq, TRep, TReq, TRep> LogFilter<TReq, TRep>(Action<string> logger)
{
    return async (request, service) =>
    {
        logger("Log before: " + request);
        var reply = await service(request);
        logger("Log after: " + reply);
        return reply;
    };
}

// authorization example
public static Filter<UnauthorizedRequest, TReply, AuthorizedRequest, TReply> AuthorizeFilter<TReply>(
    Func<Service<UnauthorizedRequest, Identity>> authorizationServiceProvider)
{
    return async (request, service) =>
    {
        var authService = authorizationServiceProvider();
        var authorizedIdentity = await authService(request);
        if (authorizedIdentity != null)
        {
            return await service(new AuthorizedRequest(authorizedIdentity, request));
        }
        else throw new UnauthorizedAccessException();
    };
}

// combine various filters together
var filters = AuthorizeFilter(AuthorizationService)
    .Then(LogFilter(Console.WriteLine))
    .Then(MonitorFilter(MonitorService)
    .Then(UnitOfWorkFilter());

// apply all filters at once to the service
var enchancedService = filters.ApplyTo(myService);

Again, thankfully to the power of composability we can join multiple filters into one and apply them to any number of services we wish to. Basically two simple types and two lines of code compiled upfront!

We ripped off (or seriously reduced) any runtime uncertainties, AOP, DI and mock libraries out of our code, as well as whole bunch of interfaces and abstraction layers. Did you enjoy the travel? Welcome in the Wonderland. Land of functional programming.

PS: presented concept is heavily inspired by Finagle library, originally written in Scala on a Twitter’s purposes.