Plugin based architecture
C# server architecture is plugin based, which means that new plugin dll can just be added and new features will be available out of the box. This makes it very convenient to add new event handlers, transaction scripts or some other server side logic, without the need for any other configuration.
In practice this means that if you want to add a new service which will take some input and return some output you will have to write a class such as:
public MyService : IServerService<MyInput, MyOutput>
private readonly IRepository<Match> Repository;
public MyService(IRepository<Match> repository)
this.Repository = repository;
public MyOutput Execute(MyInput data)
Building dll and adding it to the server will make this service available for use.
For convenience a rest plugin will handle generic invocation of such service via url such as:
and return something along
For this to work, MyService needs to be provided with all of its dependencies, such as IRepository in this case.
Service signature will define input and output types, so serialization will be able to serialize/deserialize messages coming through the wire.
Transaction scope will be implicitly provided, so all database changes will be rollbacked in case of a failure.
Framework should take the burden of injecting dependencies without any configuration, passing messages through the service and taking care of correct error and transaction handling.
Dependency Injection containers to the rescue
While Dependency Injection can be done without the use of a container, containers simplify a lot of code.
For previous example, container can provide dependencies such as IRepository with the correct database scope.
Without them, there would be a lot more boilerplate, without too much performance gains.
C# server uses modified version of Autofac for dependency injection through simple IServiceLocator interface and constructor injection. Since Autofac was optimized to improve performance, it's not an issue anymore, but it still has some small overhead.
What are the benefits of container in practice?
Instead of Thread scope, custom scope is used to manage calls. Since fixed scopes are too inflexible for edge cases and Thread based architecture is not really the performance king, better approach is required.
The end result is that services resolved from plugins will use the resolution scope to resolve dependencies. This means that they will reuse current transaction and other context dependent services available in the container.
For example, this event handler which requires persistable repository and a mailing service will use current transaction, which he doesn't have to know anything about:
public CloseApplicationSendEmailHandler : IDomainEventHandler<CloseApplication>
private readonly IPersistableRepository<Application> AppRepository;
private readonly Lazy<IMailService> MailService;
this.AppRepository = appRepository;
this.MailService = mailService;
public void Handle(CloseApplication domainEvent)
var app = AppRepository.Find(domainEvent.ApplicationURI);
if(app == null)
throw new ArgumentException("Application not found: " + domainEvent.ApplicationURI);
if(app.ClosedAt != null)
throw new ArgumentException("Application was already closed at: " + app.ClosedAt);
app.ClosedAt = DateTime.Now;
MailService.Value.Queue(new MailMessage(app.OwnerEmail, "Application " + app.Name + " closed", domainEvent.Note));
While there is still some boilerplate in it (dependency triplets), most of the code is actual business logic: error handling with informative error messages and actual business logic - sending email or rather putting it on a queue.
AOP and advanced use cases
Aspect oriented programming is integrated into the architecture which allows for easy overrides. For example when we want to track database performance, we can inject aspects around IDatabaseQuery interface and monitor duration and content of the queries. While small benefits, such as that database query implementations don't have to implement logging themselves, are nice; the biggest benefit is when we want to diagnose some behavior which we didn't anticipated before.
While we could always provide an alternative implementation by registering it into the container, it's very convenient to add workarounds using AOP, until better solution is found and implemented inside the service. In practice this means that configuration parameter such as
<add key="Performance.TimerLimit" value="100" />
can be used to easily diagnose database queries taking longer than 100ms.
When there is a need for custom transactions, scopes or something similar, IObjectFactory as a wrapper around Autofac can be used. By creating custom scope, registering instance of a database query inside it, all resolution inside that scope will resolve provided query instance. This allows us to create anonymous transactions whenever required, but there is no need to restrict ourselves to database for information storage, especially if information is not as important and high volume, such as various logs and binaries.