Позволяет создать простую структуру рабочего процесса из специально размеченных методов-обработчиков, в которой типы входящих и исходящих данных являются связями между рабочими элементами.
Данная реализация представляет собой IoC framework, позволяющий выполнять последовательность специально размеченных методов класса, в порядке определяемом входящими и исходящими типами значений этих методов.
public class SimpleComponent
{
[TwEntrypoint]
public int FirstRun()
=> 1;
[TwEntrypoint]
public float SecondRun(int i)
=> (float)i;
}
public class OtherSimpleComponent
{
[TwEntrypoint]
public void ThirdRun(float i){}
}
Для работы такой системы необходимо соблюдать одно обязательное правило - уникальность типа возвращаемого значения метода-обработчика.
Следующий код не может быть выполнен:
public class SimpleComponent
{
[TwEntrypoint]
public int Run()
=> 1;
}
public class OtherSimpleComponent
{
[TwEntrypoint]
public int Run()
=> 2;
}
Второе обязательное правило заключается в том что - все возвращаемые типы должны быть во входящих параметрах методов-обработчиках.
При попытке выполнить эту схему framework выдаст исключение из-за наличия неиспользуемого параметра int
public class SimpleComponent
{
[TwEntrypoint]
public (int, float) Run()
=> (1, 2.0);
}
public class OtherSimpleComponent
{
[TwEntrypoint]
public void Run(float i){}
}
Каждый обработчик может вернуть несколько значений (типов) за одно выполнение в виде значимого кортежа.
Каждый тип в таком кортеже будет считаться отдельным результатом выполнения и может использоваться отдельно от соседних типов в качестве входящих значений в другие методы обработчики.
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(typeof(SimpleComponent).Assembly)
.Build();
container.Run().AsTask().Wait();
Данный код создаст контейнер для выполнения последовательности методов-обработчиков и выполнит один цикл работы с ожиданием результата выполнения.
Метод Run
контейнера является потокобезопасным и может использоваться сколь угодно раз за время существования контейнера.
Использование внедрения зависимостей поддерживается для конструкторов (Constructor Injection) и статических методов размеченных атрибутом TwInject
классов которые содержат размеченные методы-обработчики.
Это можно сделать через реализацию интерфейса TypedWorkflow.IResolver
и передачу экземпляра такого класса при создании контейнера
var resolver = new MyDiResolver();
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(typeof(SimpleComponent).Assembly)
.RegisterExternalDi(resolver)
.Build();
Возможно создавать статические методы обработчики
public static class SingletonComponent
{
[TwEntrypoint]
public static void Run(int i, float f){}
}
На каждом цикле работы контейнера можно вводить в систему произвольное количество значений
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(typeof(SimpleComponent).Assembly)
.Build<(int, float)>();
container.Run((1, 2.0)).AsTask().Wait();
Необходимо что бы хотя бы один метод-обработчик имел зависимость от вводимых значений.
public class SimpleComponent
{
[TwEntrypoint]
public void Run(int i, float f){}
}
По завершению работы система может сформировать значение с указанным типом как результат работы всей системы
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(typeof(SimpleComponent).Assembly)
.Build<int, float>();
float result = container.Run(2).AsTask().Result;
public class SimpleComponent
{
[TwEntrypoint]
public float Run(int i)
=> i * 2.0;
}
Для отмены продолжительного выполнения графа компонентов используется специальный токен System.Threading.CancellationToken
, который передается при запуске меода выполнения Run
в качестве дополнительного необязательного параметра.
var componentType = typeof(TypedWorkflowTests.OtherComponents.AsyncCancellationTest.WoInputAndOutput.LongTimeExecutionComponent);
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(componentType.Assembly)
.AddNamespaces(componentType.Namespace)
.Build();
var cancellation = new CancellationTokenSource();
var t = container.Run(cancellation.Token).AsTask();
Task.Delay(500).Wait();
cancellation.Cancel();
var ex = Assert.CatchAsync<TaskCanceledException>(() => t);
Фреймворк никак не взаимодействует с этим токеном поэтому вся работа с ним должна быть реализована внутри метода-обработчика пользовательского компонента (передается в качестве одного из параметров вызова метода-обработчика).
public class LongTimeExecutionComponent
{
[TwEntrypoint]
public async Task Run(CancellationToken cancellation)
{
await Task.Delay(-1, cancellation);
}
}
Используется шаблон cache-aside
реализуемый в проактивном кеше из библиотеки ProactiveCache
.
Использование кеша позволяет сохранять результаты выполнения графа компонентов в памяти на указанное время с указанным фоновым обновлением по требованию (упреждающее обновление).
Кеширование доступно только для систем с входными и выходными параметрами (передача параметров и возвращение результатов), где входные параметры станут значением ключа в кеше,
а результаты работы кешируемым значением.
var builder = new TwContainerBuilder();
var container = builder
.AddAssemblies(typeof(SlowProducerComponent).Assembly)
.AddNamespaces(typeof(SlowProducerComponent).Namespace)
.WithCache(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1))
.Build<int, long>();
var result1 = container.Run(42).Result;
var result2 = container.Run(42).Result;
В примере в качестве ключа будет использован тип int
, а в качестве кешируемого значения возвращаемый результат с типом long
.
result1
будет получен после выполениня заданного графа компонентов из пространства имен typeof(SlowProducerComponent).Namespace
.
result2
будет взят из кеша в памяти и будет равен значению в result1
.
По истечению времени в одну секунду (второй параметр метода WithCache
), последующий запрос приведет к повторному выполнению графа, а незамедлительно следующий за ним запрос получит старое значение из кеша.
Завершение повторного выполнения графа приведет к обновлению значения в кеше и указанию времени жизни этого значения в две секунды (первый параметр метода WithCache
).
Позволяют пропускать выполнение метода-обработчика в зависимости от значения результата.
Если модель результата поместить в обертку TypedWorkflow.Option
, то это позволит возвращать пустое значение в качестве ответа.
Методы-обработчики принимающие модели значения которых не могут быть пустыми (не обернуты TypedWorkflow.Option
) будут пропущены если значения этих моделей будут пустыми.
Таким образом будут пропущены все зависимые от результата выполнения методы обработчики.
public class SimpleComponent
{
[TwEntrypoint]
public TypedWorkflow.Option<int> Run()
=> TypedWorkflow.Option<float>.None;
[TwEntrypoint]
public float NeverRun(int i)
=> i * 2.0;
[TwEntrypoint]
public void NeverRun(float i) {}
}
Реализованы в виде атрибута TwConstraintAttribute
и предназначены для блокировки выполнения метода-обработчика на основе значения наличия или отсутствия значения заданной модели.
Используя такие ограничения можно реализовать работу кеша, это когда наличие значения в кеше блокирует выполнение операции получения этого значения из его более медленного хранилища (например, базы данных).
Условие ограничения можно назначать как на базовый или основной класс компонента, так и на сам метод-обработчик.
Результат заблокированного метода обработчика будет иметь пустое значение, поэтому все методы обработчика, зависящие от этого значения, будут пропущены, а их результаты также будут иметь пустое значение.
[TwConstraint(typeof(FromCache), HasNone = true)]
public class ConstrainedComponent
{
[TwEntrypoint]
public Option<FromDb> GetModelFromDb()
{
return Option.Create(new FromDb(new SomeModel()));
}
}
Более подробный пример можно найти в тестах TypedWorkflowTests.Components.11-ConstrainedComponent.cs
- Возвращайте экземпляры классов, а не структур (механизм boxing и unboxing в случаях вызова методов-обработчиков через использование рефлексии сводит все преимущества структур к нулю).
- По возможности используйте
singlеton
компоненты (использование статических методов в качестве методов-обработчиков).
Можно найти в проекте TypedWorkflowTests
в классе WorkflowBuilderTest
(все компоненты размещены в папке Components
указанного проекта)