Comments (10)
Event system client facing API
Proposals for the way client code interacts with the event system.
There's quite a few ways to go about this in .NET, I'll propose the ones I've seen used the most.
Obviously feel free to pick on namings, mix and match implementations, suggest other approaches etc.
Other things of interest to discuss might be async invocation vs synchronous invocation of message handlers/callbacks, should we catch+log+rethrow? catch+log+swallow? etc.
1. Configured in a separate class on property in UnleashSettings initializer
var settings = new UnleashSettings()
{
AppName = "event-system-client-facing-examples",
InstanceTag = "instance z",
UnleashApi = new Uri("http://localhost:4242/api/"),
CustomHttpHeaders = new Dictionary()
{
{"Authorization","API token" }
},
//==== This is the part we're adding ====/
Events = new EventsConfig
{
// A. Callback option
Impression = evt => { Console.WriteLine($"Impression: { evt.EventType }"); },
// B. Class/Interface option
Impression = new ImpressionEventHandler(),
}
};
Infrastructure stuff
public class EventsConfig
{
// A. Callback option
public Action<ImpressionEvent> Impression { get; set; }
// B. Class/Interface option
public IEventHandler<ImpressionEvent> Impression { get; set; }
}
public class UnleashSettings
{
//....
// Setting added as property and set on initialization
public EventsConfig Events { get; set; }
}
public class ExpressionEventHandler : IEventHandler<ImpressionEvent>
{
public void Handle(ImpressionEvent evt)
{
Console.WriteLine($"Impression: { evt.EventType }");
}
}
2. Register-method on UnleashSettings
Generics, marker interface, and inheritance gives us explicit method invoking for client and provides us with the metadata we need to keep track of handlers, but require a few polymorphic workarounds if storing in dictionaries/collections/...
Should this method be called On()? Register()? Any other name you'd rather want?
var settings = new UnleashSettings()
{
AppName = "event-system-client-facing-examples",
InstanceTag = "instance z",
UnleashApi = new Uri("http://localhost:4242/api/"),
CustomHttpHeaders = new Dictionary()
{
{"Authorization","API token" }
},
};
//==== This is the part we're adding ====/
// A. Callback option
settings.On((ImpressionEvent evt) =>
{
Console.WriteLine($"Impression: { evt.EventType }");
});
// B. Class/Interface option
settings.On(new ImpressionEventHandler());
Infrastructure stuff
// Marker interface
public interface IUnleashEvent { }
// Add registering methods to UnleashSettings
public class UnleashSettings
{
//....
// A. Callback option
public void On<TEventType>(Action<TEventType> callback) where TEventType : IUnleashEvent {}
// B. Class/Interface option
public void On<TEventType>(IEventHandler<TEventType> evtHandler) where TEventType : IUnleashEvent {}
}
public class ExpressionEventHandler : IEventHandler<ImpressionEvent>
{
public void Handle(ImpressionEvent evt)
{
Console.WriteLine($"Impression: { evt.EventType }");
}
}
3. Configure -method
This has become more and more popular in .NET lately, with .NET Core/5/6 especially when configuring hosting/DI etc.
Essentially a flavour of Alt.1, a callback is called that configures an object passed to it.
var settings = new UnleashSettings()
{
AppName = "event-system-client-facing-examples",
InstanceTag = "instance z",
UnleashApi = new Uri("http://localhost:4242/api/"),
CustomHttpHeaders = new Dictionary()
{
{"Authorization","API token" }
},
};
//==== This is the part we're adding ====/
settings.ConfigureEvents(evCfg =>
{
// A. Callback option
evCfg.Impression = evt => { Console.WriteLine($"Impression: { evt.EventType }"); };
// B. Class/Interface option
evCfg.Impression = new ImpressionEventHandler();
});
Infrastructure stuff
// Add a configure method that takes a callback to UnleashSettings
public class UnleashSettings
{
//....
public void ConfigureEvents(Action<EventsConfig> configureCallback) {
var config = this.EventsConfig ?? new EventsConfig()
{
// defaults?
};
// Call the provided callback
configureCallback(config);
// Stash it
this.EventsConfig = config;
}
}
// Store deep down in the depths of UnleashSettings after configuring
public class EventsConfig
{
// A. Callback option
public Action<ImpressionEvent> Impression { get; set; }
// B. Class/Interface option
public IEventHandler<ImpressionEvent> Impression { get; set; }
}
// No surprises here
public class ExpressionEventHandler : IEventHandler<ImpressionEvent>
{
public void Handle(ImpressionEvent evt)
{
Console.WriteLine($"Impression: { evt.EventType }");
}
}
from unleash-client-dotnet.
Cool! This looks promising!
To add some more questions into the mix, another thing we need to consider fairly carefully is backwards compatibility, our docs say we support backwards to .Net 4.5, so whatever we do here, it should work on that (if we need to drop support for 4.5 we can do that but I think we'd need a fairly strong reason to do so.)
On the above note, I really like the 3rd option, it looks nice, and if this is where .NET is going it would be cool to be able to move with the times but backwards compatibility may be a problem there. It's worth checking, I think.
An Unleash client should have similar properties to a good logger when it comes to error handling - that is, don't crash the hosting process. So I think when it comes to error handling, we need to do our best to log and prevent that exception from bubbling. There might be something I haven't considered here, but I don't see a reason to throw/rethrow right now (tell me if I'm crazy).
I see that you've bound the events handler to the settings object in the examples. Is it crazy to do something like:
var unleashFactory = new UnleashClientFactory();
var unleash = await unleashFactory.CreateClientAsync(settings, synchronousInitialization: false);
unleash.ConfigureEvents(evCfg =>
{
//something cool
});
Kinda want to say having the events bound to the Unleash instance is a lot clearer than binding it to the settings object. WDYT?
from unleash-client-dotnet.
Agreed with Simon. This looks promising.
I'm inclined to agree that it seems more logical to bind the event handling/configuring to the unleash object; but I realise I've been away from .NET for a long time so maybe it's more natural to bind it using the settings object. Just as long as we document properly how to do it.
In the js library we make sure the Unleash instance being returned is an EventEmitter, which of course allows the .on('eventType', (event) => {})
syntax which I'm rather fond of. The subscriber interface we have in Java is close, but it's not quite as smooth.
Tbh, what you have here seems about as smooth as what you can expect in a statically typed language.
But yes, we do need to avoid crashing the hosting process, so event handling throwing an exception should still not break the polling nor the host application.
from unleash-client-dotnet.
@sighphyre @chriswk Awesome, thanks that was quick :)
Backwards compatibility
Language feature wise, this should be fairly vanilla and I'd be surprised if it fails to compile in .NET 4.5. But I will have a look at that to make sure. It's more of a coding pattern change I've seen a lot recently especially in .NET Core and later when configuring services and DI and hosting etc.
Crashing/..
First implementation of this will be for impression data events that to me feels very statistics/dashboardy, so I think I'd be surprised indeed as a consumer of this, if something in my bridging code would crash the current request/execution.
Settings vs Unleash instance
I realise I didn't write anything specifically about this, but I'm fully open to moving it. The reason I chose the UnleashSettings object was merely because that's where most of the configuration takes place. It would definitely be closer to where it would be used if scoped to the Unleash instance, and since you both point to it that seems to be the better idea!
In the js library we make sure the Unleash instance being returned is an EventEmitter, which of course allows the
.on('eventType', (event) => {})
syntax which I'm rather fond of. The subscriber interface we have in Java is close, but it's not quite as smooth.
An early version of one of the above suggestions had the following format:
.On(Events.Impression, ImpressionEvent evt => { ... })
but was changed when I realised it wouldn't need that enum key as we already had a generics argument for the event type, and felt like a more compile time checking-friendly way. It is however obviously possible to revert back to that as a more explicit option if preferred/is more consistent/in line with how it works in Node and on other platforms
from unleash-client-dotnet.
Sounds great! Be really great to have something like this in the .NET client!
Yeah, I want to say the suggestion should work in older .NET versions, it's definitely worth checking though, since I don't see any obvious backwards compat tests in this client (something for later probably)
I do think the events look nicer bound to the Unleash instance rather than the settings but I don't have very strong feelings here, I'm very open to you picking what feels right and then documenting that properly
from unleash-client-dotnet.
Again, just reinforcing what Simon said, as long as what you decide is properly documented I trust you to make the best choice 👍
from unleash-client-dotnet.
Thanks @chriswk @sighphyre! I'll have a look and see what i think fits the internal workings the best, and run with that!
from unleash-client-dotnet.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
from unleash-client-dotnet.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
from unleash-client-dotnet.
Implemented in #113
from unleash-client-dotnet.
Related Issues (20)
- Does the Context Need to be Cloned? HOT 4
- Reducation in target frameworks HOT 1
- Replacement of LibLog with Microsoft.Extensions.Logging.Abstractions HOT 1
- Record metrics using System.Diagnostics.Metrics HOT 2
- Split of nuget package HOT 2
- Negate operator not work in dot net sdk HOT 3
- api/client/metrics return 400 when using System.Text.Json for seriaization. HOT 5
- Feature toggles are fetched in a chron manner instead of when a feature flag is actually needed HOT 9
- Removal of Microsoft.CSharp package dependency HOT 4
- Update licence inclusion in Nuget HOT 2
- Platform specific test suite for .NET Framework to run in CI HOT 2
- Receiving Json Parsing error when receiving 304 status when ETag in response header is null. HOT 1
- We see a lot "The `appName` property is required. It was not present on the data you sent." HOT 2
- Unhandled exceptions in Unleash's background process HOT 2
- synchronousInitialization: true should throw exception if exception happens in the init flow (network, authorization, etc) HOT 3
- InvalidOperationException being thrown from Unleash.Scheduling.SystemTimerScheduledTaskManager.ConfigureTask HOT 2
- Empty userId breaks gradual rollout 100% expected behavior. HOT 3
- Built-in JSON serialization without reflection HOT 3
- Avoid subtle errors and improve DX when setting a custom serializer HOT 3
- Support for hostname strategy HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from unleash-client-dotnet.