alexeyfv

Опубликовано

- 2 мин чтения

Генераторы кода в C#

C# Source Generators Event Sourcing DDD
img of Генераторы кода в C#

В ноябре я рассказывал про EventFlow — библиотеку, предназначенную для DDD и Event Sourcing. Наша команда активно её использует. Спустя несколько месяцев работы с ней я заметил, что часто приходится писать повторяющийся код, что довольно утомительно.

Например, чтобы создать событие для OrderAggregate, нужно каждый раз указывать тип агрегата и тип его ID:

   public class OrderCreated :
    IAggregateEvent<OrderAggregate, OrderAggregateId>

При подписке на события снова нужно указывать тип события, агрегата и ID:

   public class OrderAggregateSubscribers :
    ISubscribeSynchronousTo<OrderAggregate, OrderAggregateId, OrderCreated>

Как избавиться от шаблонного кода?

Можно объявить абстрактный класс для всех событий агрегата и использовать его:

   public abstract class OrderAggregateEvent :
    IAggregateEvent<OrderAggregate, OrderAggregateId>;

Создать новый интерфейс, принимающий только тип события:

   public interface ISubscribeSynchronousTo<TEvent> :
    ISubscribeSynchronousTo<OrderAggregate, OrderAggregateId, TEvent>

Но даже в этом случае всё равно приходится вручную дублировать код для каждого агрегата. А как гласит известная айтишная мудрость:

Никогда не тратьте 10 минут, чтобы сделать что-то вручную, если можно потратить 10 часов пытаясь это автоматизировать.

Поэтому в начале февраля я занялся написанием генератора кода для EventFlow. Через 2 недели и 7 коммитов библиотека EventFlow.SourceGenerators была готова и на днях Расмус Микелсен (автор EventFlow) принял изменения.

Как помогают генераторы кода?

Теперь вся рутинная работа ложится на компилятор, и можно писать меньше кода, который к тому же лучше читается.

   // Добавляем атрибут AggregateExtensions к агрегату
[AggregateExtensions]
public class OrderAggregate(OrderAggregateId id) :
    AggregateRoot<OrderAggregate, OrderAggregateId>(id)

// Описание событий — OrderAggregateEvent генерируется автоматически
public class OrderCreated : OrderAggregateEvent;

// Подписка на события — новый интерфейс генерируется автоматически
public class OrderAggregateSubscribers :
    ISubscribeSynchronousTo<OrderCreated>

Как видите, код стал заметно чище, и теперь нам не приходится снова и снова указывать тип агрегата и тип ID.

Сложности при работе с генераторами кода

Стоит отметить, что генераторы кода — непростая тема. Существует множество ограничений и сложностей. Например, с чем я столкнулся:

  1. Генераторы можно писать только под .NET Standard 2.0.
  2. Возможны коллизии названий сгенерированных объектов.
  3. Тестирование сгенерированного кода имеет свои особенности.

Что дальше?

Если вас заинтересовали генераторы кода, советую начать с видео от Microsoft.

Затем изучите следующие материалы:

В качестве примера можно заглядывать в исходный код библиотеки EventFlow.SourceGenerators и её тестов. Она довольна простая. Больше примеров её применения можно найти в документации.