This sample demonstrates how to generate an AsyncAPI schema document from NServiceBus endpoints using Neuroglia.AsyncApi in two different hosting environments:
It extends the simple AsyncAPI sample by differentiating between event types (i.e. published vs subscribed) and showing how an event can be published by one endpoint and subscribed to by another without using the same concrete class to define the event, therefore decoupling the systems from each other.
AsyncAPI integration is not an officially supported feature of NServiceBus, and this example is for demonstration purposes only.
Code walk-through
This sample contains four projects:
- AsyncAPI.Feature - a class library containing shared code required to enable the custom
AsyncApiFeature
- AsyncAPI.Web - a ASP.NET Core WebAPI application that publishes two events and generates an AsyncAPI document schema for its structure, accessible via a URL
- AsyncAPI.GenericHost - a console application that publishes two events, sends a message and generates an AsyncAPI document schema for its structure and saves it to disk
- Subscriber - a console application that subscribes to the events published by AsyncAPI.GenericHost and AsyncAPI.Web
Feature project
The project contains all necessary code to support decoupling event types from the publisher and subscriber (contained in the MessageConventions
folder), as well as registering a custom AsyncAPI document schema generator.
The EndpointConfigurationExtensions
contains the glue that registers the AsyncApiFeature
and the endpoint configuration message conventions.
public static void EnableAsyncApiSupport(
this EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.DisableFeature<AutoSubscribe>();
endpointConfiguration.EnableFeature<AsyncApiFeature>();
var conventions = endpointConfiguration.Conventions();
conventions.Add(new PublishedEventsConvention());
conventions.Add(new SubscribedEventsConvention());
}
AsyncAPI feature
The feature creates mappings for physical to logical event types based on the PublishedEvent
and SubscribedEvent
attributes that decorate the events that are published and subscribed to. These mappings are registered in the container so that they are available for the following pipeline behaviors defined in the feature:
- ReplaceOutgoingEnclosedMessageTypeHeaderBehavior
- ReplaceIncomingEnclosedMessageTypeHeaderBehavior
- ReplaceMulticastRoutingBehavior
context.Services.AddSingleton(new TypeCache
{
EndpointName = context.Settings.EndpointName(),
PublishedEventCache = publishedEventCache,
SubscribedEventCache = subscribedEventCache
});
Finally, the code registers a custom implementation of the Neuroglia IAsyncApiDocumentGenerator
which will be used instead of the default implementation to generate the NServiceBus-specific schema document.
context.Services.AddTransient<IAsyncApiDocumentGenerator>(
provider => new ApiDocumentGenerator(provider));
AsyncAPI document generator
This custom implementation of the Neuroglia IAsyncApiDocumentGenerator
creates one AsyncAPI schema document for the NServiceBus endpoint hosted in the application. It demonstrates how channel information with the endpoint's address (queue name) can be generated, containing the publish operation and the event payload.
This code can be extended to include subscriptions as well as sent/received messages.
WebAPI project
Setup AsyncAPI
The project enables AsyncAPI schema generation using two setup calls.
First, by adding the Neuroglia AsyncAPI.
// Configures code-first AsyncAPI document generation.
builder.Services.AddAsyncApiGeneration(asyncApiBuilder =>
asyncApiBuilder
.UseDefaultV3DocumentConfiguration(asyncApi =>
{
//Setup V3 documents, by configuring servers, for example:
asyncApi.WithTitle("Web Service");
asyncApi.WithVersion("1.0.0");
asyncApi.WithLicense(
"Apache 2.0",
new Uri("https://www.apache.org/licenses/LICENSE-2.0"));
asyncApi.WithServer("amqp", setup =>
{
setup
.WithProtocol(AsyncApiProtocol.Amqp)
.WithHost("sb://example.servicebus.windows.net/")
.WithBinding(new AmqpServerBindingDefinition
{
BindingVersion = "0.1.0",
});
});
}));
// Adds AsyncAPI UI services. Available at /asyncapi.
builder.Services.AddAsyncApiUI();
Second, by registering the AsyncAPI feature with the NServiceBus endpoint.
endpointConfiguration.EnableAsyncApiSupport();
Define published events
The published events are defined by using the PublishedEvent
attribute to tell NServiceBus which event they represent.
The FirstEventThatIsBeingPublished
class is marked as representing the SomeNamespace.
event.
[PublishedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstEventThatIsBeingPublished
{
public string SomeValue { get; init; }
public string SomeOtherValue { get; init; }
}
The SecondEventThatIsBeingPublished
class is marked as representing the SomeNamespace.
event.
[PublishedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondEventThatIsBeingPublished
{
public string SomeValue { get; init; }
public string SomeOtherValue { get; init; }
}
Access AsyncAPI schema document
The resulting AsyncAPI schema document can be accessed under /asyncapi. From here it can be downloaded and inspected using AsyncAPI Studio (by pasting in the contents) and incorporated into internal system documentation.
Generic host project
Setup AsyncAPI
The project enables AsyncAPI schema generation using three setup calls.
First, by adding the Neuroglia AsyncAPI.
builder.Services.AddAsyncApi();
Second, by registering the AsyncAPI feature with the NServiceBus endpoint.
endpointConfiguration.EnableAsyncApiSupport();
Lastly, by adding a background service to generate and write the AsyncAPI document schema to disk.
builder.Services.AddHostedService<AsyncAPISchemaWriter>();
Define published events
The published events are defined by using the PublishedEvent
attribute to tell NServiceBus which event they represent.
The FirstEventThatIsBeingPublished
class is marked as representing the SomeNamespace.
event.
[PublishedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstEventThatIsBeingPublished
{
public string SomeValue { get; init; }
public string SomeOtherValue { get; init; }
}
The SecondEventThatIsBeingPublished
class is marked as representing the SomeNamespace.
event.
[PublishedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondEventThatIsBeingPublished
{
public string SomeValue { get; init; }
public string SomeOtherValue { get; init; }
}
One message (MessageBeingSent
) is also sent and received locally, demonstrating that standard NServiceBus message processing can run alongside custom Publish/Subscribe translations.
Access AsyncAPI schema document
The AsyncAPISchemaWriter
uses the custom document generator injected as part of the AsyncApiFeature
to generate the document schema and write it to disk.
var documents = await apiDocumentGenerator.GenerateAsync(
markupTypes: null, options, cancellationToken);
if (documents is not null && !documents.Any())
{
logger.LogInformation("No documents generated.");
}
else
{
logger.LogInformation("Found #{Count} generated document(s).", documents.Count());
foreach (var document in documents)
{
using MemoryStream stream = new();
await asyncApiDocumentWriter.WriteAsync(
document, stream, AsyncApiDocumentFormat.Json, cancellationToken);
var schemaFile = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"downloads",
$"{document.Title}.json");
File.WriteAllBytes(schemaFile, stream.ToArray());
}
}
The file is saved into the default Downloads
folder - it can be viewed using AsyncAPI Studio (by pasting in the contents) and incorporated into internal system documentation.
Subscriber project
Setup AsyncAPI
The project uses the EnableAsyncApiSupport
extension method to allow it to subscribe to published events from the WebAPI and Generic host projects using its own implementation of the event classes.
endpointConfiguration.EnableAsyncApiSupport();
Define subscribed to events
It defines its own concrete event classes and uses the SubscribedEvent
attribute to tell NServiceBus which event they represent.
The FirstSubscribedToEvent
class is marked as representing the SomeNamespace.
event.
[SubscribedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstSubscribedToEvent
{
public string SomeOtherValue { get; init; }
}
The SecondSubscribedToEvent
class is marked as representing the SomeNamespace.
event.
[SubscribedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondSubscribedToEvent
{
public string SomeValue { get; init; }
}
Note that the defined events do not need to match all the properties defined in the published events, but contain only those that they are interested in.
Running the sample
When running the solution with Visual Studio, three applications will start automatically.
AsyncAPI.GenericHost (console)
- Press
s
to send a message to itself. - Press
1
to publishFirstEventThatIsBeingPublished
- received byAsyncAPI.
.Subscriber - Press
2
to publishSecondEventThatIsBeingPublished
- received byAsyncAPI.
.Subscriber
AsyncAPI.Web (web)
- Open /scalar to publish both events - received by
AsyncAPI.
.Subscriber - Open /asyncapi to view the AsyncAPI schema for the application. This can be exported as JSON or YAML.
AsyncAPI.Subscriber (console)
Displays all events published from AsyncAPI.
and AsyncAPI.
.