From 8962c5458dbbbc64233783809c575776bec604e1 Mon Sep 17 00:00:00 2001 From: Zoltan Tanczos Date: Thu, 11 Jul 2024 08:17:14 +0200 Subject: [PATCH] Add ADR on Module Loader lifetime events --- architecture/adr-015-module-loader-events.md | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 architecture/adr-015-module-loader-events.md diff --git a/architecture/adr-015-module-loader-events.md b/architecture/adr-015-module-loader-events.md new file mode 100644 index 000000000..e2fc14c64 --- /dev/null +++ b/architecture/adr-015-module-loader-events.md @@ -0,0 +1,78 @@ + + +| Type | ID | Status | Title | +| ------------- | ------------- | ------------- | ----------------------- | +| ADR | adr-015 | Approved | Module Loader events | + + +# Architecture Decision Record: Module Loader events + +## Context +Module Loader is responsible for starting and stopping module instances. It is also responsible for emitting lifetime events of module instances, such as `Starting`, `Started`, `Stopping`, `Stopped`. + +The current architecture is built on top of `IObservable`: + +```c# +public interface IModuleLoader +{ + IObservable LifetimeEvents { get; } + + // other members omitted for brevity +} +``` + +### Current usages + +#### Raising events +The Module Loader is responsible for raising lifetime events. This is currently achieved by using a [Subject](https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229173(v=vs.103)). Subjects implement both `IObservable` and `IObserver`, and when a lifetime event needs to be emitted we call `OnNext()` on the subject. + +#### Consuming events +Module Loader lifetime events are consumed by ComposeUI infrastructure code, but can also be consumed by external applications. + +Current infrastructure usages utilize `System.Reactive` extensions and its `LINQ` operators. For example: + +```c# +internal sealed class ModuleService : IHostedService +{ + // ... + public Task StartAsync(CancellationToken cancellationToken) + { + _disposables.Add( + _moduleLoader.LifetimeEvents + .OfType() + .Where(e => e.Instance.Manifest.ModuleType == ModuleType.Web) + .Subscribe(OnWebModuleStarted)); +``` + +This usage filters lifetime events by type and by content to subscribe only to the `Started` events of `Web` module instances. + +Another usage uses the `ObserveOn()` extension method which ensures that the observer's `OnNext()` method is invoked via the specified scheduler, in this case using the `DispatcherSynchronizationContext` from WPF. + +In our FDC3 Desktop Agent implementation we use `System.Reactive.Async` to convert the `IObservable` events to `IAsyncObservable` so that the `OnNext()` calls are properly awaited: + +```c# + var observable = _moduleLoader.LifetimeEvents.ToAsyncObservable(); + var subscription = await observable.SubscribeAsync(async lifetimeEvent => + { + // code containing await +``` +## Alternatives considered + +### Events +The most obvious alternative to `IObservable` is the built-in `event` feature in C# / .NET. Raising lifetime events would be very similar to the current code, however subscribing to these events would be limited to the `+=` operator. Since we're already relying on the `LINQ` operators provided by Rx.NET using built-in `events` instead would be inferior. + +The current implementation of the FDC3 Desktop Agent relies on awaiting in the `OnNextAsync()` handler. Implementing the same behavior by using built-in `events` would be challenging (if possible at all). + +### `IAsyncObservable` + +`IAsyncObservable` is part of `System.Reactive.Async` package, for which the latest available version as of today is 6.0.0-alpha.18, which is a pre-release version. + +We are already using this package internally but we don't want to enforce pre-release dependencies on our customers by changing the public interface to use `IAsyncObservable`. + +## Decision + +We continue using `IObservable` for Module Loader lifetime events. + +## Consequences + +We will revisit this ADR once there will be a stable version of `System.Reactive.Async` package available. \ No newline at end of file