-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BusBuilder prototype first-step #431
BusBuilder prototype first-step #431
Conversation
Would the entry point here be static? Or is the idea that the builder is resolved from DI with some dependencies injected? |
I assume this would also look to solve the lack of async and deferred execution of the current builder, in which case we no longer require a logger as the first thing to be configured. |
My idea would be that there's one somewhere and you get it however you want (your own static, DI, whatever) and then chain from there. Basically I want to get rid of all the static stuff as it's the source of 90% of the pain in the integration tests with the shared static for mocking out the factory to do things. This is just the barest skeleton of the sort of shape things could take with an arbitrary name I've picked, then if we like where it's going I can start actually trying to plumb it in and cast off the |
Indeed, with the builder pattern we could make things very lazy/configurable over time, and then it would be up to the integrator to either use it once and use that forever, or use the builder as part of DI registration and get things as they are needed. |
Cool, I agree on all points. It'd be nasty if there were some dependencies that were baked into a static factory method for example. I was thinking that potentially we'd just be building up a data structure with the fluent api, and the starting point would be blank, so it's kind of just a style thing whether it's static or not. If we are going to allow cross cutting things (let's say ILogger, perfect example) to be injected via DI, then I could see it being beneficial to go down the instance method entrypoint route. |
Any thoughts on this from anyone else (@AnthonySteele, @brainmurphy) before I start thinking about making this PR actually do something working? |
FYI I've started working on a working prototype for this. I'm doing it as an alternate implementation for now, rather than replacing anything already in the library. |
So I've got a basic skeleton of a API surface we could look towards doing. An example of it is below, which is just a simple unit test. The general gist is:
The Then once everything is done, you just get the simple One (of the many things) missing so far is a need to hook DI in to the builder for use during [Fact]
public static void Can_Create_Messaging_Bus_Fluently()
{
// Arrange
var builder = new MessagingBusBuilder()
.Client()
.WithBasicCredentials("accessKey", "secretKey")
.And().Parent
.Messaging()
.WithRegions("eu-west-1", "eu-central-1")
.And()
.WithActiveRegion("eu-west-1")
.And().Parent
.WithLoggerFactory(new LoggerFactory());
// Assert
IMessagingBus bus = builder.Build();
bus.Start(new CancellationToken(canceled: true));
} |
I love the DI extension method and how tidy this looks 👏 It's a taste thing, but I'm not sure about the .Messaging(x => x
.WithRegions("eu-west-1", "eu-central-1")
.WithActiveRegion("eu-west-1")) Another thing to add to the wish list, the existing implementation allows you to to build a publisher and subscriber all on one go, which struck me a wierd. So Finally, (as mentioned in another issue) we need a |
This looks good and a lot nicer than the current API...adios One thought I had (which sort of leans towards what Stu had mentioned) was to configure the bus with common configuration and then branch off of that into the more specific publisher / subscriber logic....maybe something like this; var configuredBus = new MessagingBus()
.Messaging(cfg => {
// Regions / Serialisation / Naming conventions
})
.Monitoring(cfg => {
// Message monitor / logging / ...?
})
IMessagePublisher publisher = await configuredBus.ConfigurePublishers()
.AddTopicPublisher<T>()
.AddQueuePublisher<T>()
.BuildAsync();
IMessageSubscriber subscriber = await configuredBus.ConfigureSubscribers()
.AddTopicSubscriber<TMessage, THandler>(cfg => {})
.AddQueueSubscriber<TMessage, THandler>(cfg => {})
.BuildAsync(); Just another idea but feel free to 🔥 it! |
I like both of those suggestions. There's already a lot of plumbing going on for this, so I don't think the possible overhead of a few extra types for the nested builders is a bad shout. The It probably means I can ditch the need for the base class for the builders, which seemed like a good idea at the start, but as nothing's really being subclassed within a particular builder type (and I've I'll mull on this and look at doing some more hacking around tomorrow to work towards the suggested nested builders. |
I've made a first pass at the suggested approach with the nesting, and also made some tweaks here and there and fixed a few bugs along the way (I'll cherry-pick those into separate PRs later today) so that the two tests both pass again (locally at least) by pointing at goaws. I'll come back to looking at separating out subscribing and messaging later on. The updated sample from the test is now (I've left [Fact]
public void Can_Create_Messaging_Bus_Fluently()
{
// Arrange
var services = new ServiceCollection()
.AddLogging((p) => p.AddXUnit(OutputHelper))
.AddJustSaying(
(builder) =>
{
builder.Client(
(options) => options.WithBasicCredentials("accessKey", "secretKey"))
.Messaging(
(options) => options.WithRegions("eu-west-1", "eu-central-1")
.And()
.WithActiveRegion("eu-west-1"))
.Subscriptions(
(options) => options.WithHandler<MyMessage>());
})
.AddJustSayingHandler<MyMessage, MyHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
// Assert
var bus = serviceProvider.GetRequiredService<IMessagingBus>();
bus.Start(new CancellationToken(canceled: true));
} |
It's looking really nice 👍 I'll have a deeper dive later today and feedback |
Just for reference, @adammorr did a prototype a while ago of the fluent api (see here) Something that I quite liked in that prototype was making it clear if you were dealing with topics or just queues in subscriptions and publishing, with methods like One of the things that I find slightly annoying with the way JustSaying currently works is that you might expect those 4 apis to be relatively symmetrical, but in the common case of While you're in the guts of this, what's your thoughts? |
I haven't gotten through all of the internals yet, but from what you're describing it might be worth "forking" things in the configuration to differentiate between the two models so that only relevant options are presented for what's being configured at the time. |
Having a quick look at @adammorr's PR, it does look like there's a lot of common ideas going on between that and this. I think the only thing I disagree with from there is the use of interfaces. I think concrete types are much better for this scenario as it makes them easy to extend with minimal fuss and/or breaking chances, rather than the explosion of interfaces that exists in JustSayingFluentlyInterfaces.cs. |
Rebased. |
@martincostello This is nice work so far, so please don't take this the wrong way. I very much like @adammorr's suggestion of splitting out the publish and subscribe parts into two branches.
var configuredBus = new MessagingBus()
.Messaging(cfg => {
// Regions / Serialisation / Naming conventions
})
.Monitoring(cfg => {
// Message monitor / logging / ...?
})
IMessagePublisher publisher = await configuredBus.ConfigurePublishers()
.EnsureTopicExists<T1>() // for when we want to publish to a topic which will allow fan-out to many queues
.BuildAsync();
IMessageSender sender = await configuredBus.ConfigureSenders()
.EnsureQueueExists<T2>() // for when we want point-to-point queues with no topic
.BuildAsync();
IMessageSubscriber subscriber = await configuredBus.ConfigureSubscribers()
.EnsureTopicExists<T1>(topic =>
topic.EnsureQueueExists<THandler1>(cfg => {})) // here we are adding a queue for a specific topic (we could continue to chain more EnsureQueueExists calls here to fan-out to many queues)
.EnsureQueueExists<T2, THandler2>(cfg => {}) // here we are adding a queue without a topic
.BuildAsync();
Usage would be: sender.SendAsync(...)
publisher.PublishAsync(...)
subscriber.StartListeningAsync(...) |
I'll look at splitting things up more into publish/subscribe/listen when more of this PoC is wired-up. I still need to implement any publishing at all, and I only got subscriptions working last night. Publishers and listeners aren't yet wired up into the DI or available directly on the builder. Once I've got the basics of JustSaying working end-to-end with a goaws integration that passes, I can start refactoring things to fit the suggestions more. Writing this PR is also very much a learning process for me of what JustSaying actually does under the covers. |
That's a fair point @shaynevanasperen, it was done like that because JustSaying didn't have separate semantics for message sends, we should take the time here to get it right. |
@shaynevanasperen just on your point about |
I was thinking about the same thing @shaynevanasperen with regards to explicitly asking for topic/queue/subscription creation, it would need to be something that wouldn't be easy to miss for existing consumers relying on that behaviour. @adammorr You are right, I think what Shayne means is that the existing JustSaying lingo is wrong, cause you send to queues, and publish to topics. So if we introduced a |
This Looks Good To Me, and I am very happy with the general approach. But I am not the expert on the fluent interface (it's the part of JustSaying that I am least familiar / concerned with) so someone else will have to say if the details fit with the various consumers. |
I'd say that if @adammorr seems OK with it, we can get this merged so we can start fleshing it out more and starting to switch over/remove the old code over the quiet period between now and January. |
I’ll take a look when I get back from holiday tomorrow 👍 |
This looks good to me 👍 the only question I have (which may have already been considered or will come as part of fleshing this out once merged) is around As subscriber configuration and its handler are closely related, would we want to either;
|
The Being able to do warnings would also mean needing introspecting the DI and/or maybe prematurely activating services, so while that might be a nice-to-have, it might get complicated. |
Use var instead of the explicit type for the handler resolver.
OK cool - it's probably not worth incurring any additional complexity for this given the existing fluent API doesn't give you any kind of warning either! |
Regarding the above, actually, maybe we could put in some lightweight stuff into the default |
Right I think we're at the point now this can be merged and we can start some Christmas-period-lull hax0r activities to start cutting over and expanding the new API. Just needs a ✔️ now... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff @martincostello
💥 |
Later today I'll look at making some issues (i.e. tasks) that can be doled out amongst the interested parties to start the switch-over. |
First batch of issues linked above. |
Add new
BusBuilder
class as a prototype of a replacement forCreateMeABus
.