Acquaintance
Acquaintance is a .NET library for intra-process communications. It’s an in-memory message bus that loosely-coupled parts of your application can use to communicate with each other. At the heart, Acquaintance implements three basic communication patterns: Publish/Subscribe, Request/Response and Scatter/Gather. On top of these three basic patterns, a number of other patterns, features and workflows are built to help support medium- and large-size projects.
Get Acquaintance from nuget:
Install-Package Acquaintance
When you’re ready to code, create a Message Bus:
var messageBus = new MessageBusBuilder().Build();
Publish/Subscribe
Publish/Subscribe is a pattern where you have events of interest being generated by one part of the system and other parts need to be aware of them. Published messages are delivered to all interested subscribers.
// Create a subscription
messageBus.Subscribe<MyEvent>(s => s
.WithTopic("test event")
.Invoke(e => Console.WriteLine(e.Message)));
// Publish a message
messageBus.Publish("test event", new MyEvent {
Message = "Hello World"
});
Request/Response
Request/Response is essentially an abstracted method call or local RPC mechanism. One part of your code makes a request and a single listener fulfills it. This can be an improvement over Dependency Injection or Service Location patterns in some cases, such as when you only need a calculation result and do not want to manage and maintain the object hierarchy necessary to calculate that result.
// Setup a Listener
messageBus.Listen<MyRequest, MyResponse>(l => l
.WithTopic("test")
.Invoke(req => new MyResponse {
Message = "Hello " + req.Message
}));
// Send a request and wait for the response
var response = messageBus.RequestWait<MyRequest, MyResponse>("test",
new MyRequest { Message = "World" });
Console.WriteLine(response.Message);
Scatter/Gather
Scatter/Gather is conceptually similar to Request/Response except the channel may have many listeners or participants. Internally and in terms of API the two are significantly different, however. Because scatter/gather requests must wait on an unknown number of participants providing responses asynchronously, waiting and timeouts require significant planning and consideration. For best performance, when you do scatter/gather make sure you know how many responses you’d like to receive and how long are you willing to wait to get them.
// Setup a Participant
messageBus.Participate<MyRequest, MyResponse>(p => p
.WithTopic("test")
.Invoke(req => new[] { new MyResponse {
Message = "Hello " + req.Message"
}}));
// Scatter the request message to all participants
var scatter = messageBus.Scatter<MyRequest, MyResponse>("test",
new MyRequest { Message = "World" });
var responses = scatter.GatherResponses();
foreach (var response in responses)
Console.WriteLine(response.Response.Message);
Use-Cases
Here are some common use-cases which Acquaintance was explicitly designed to handle:
- Simplifying complex constructor signatures which may arise from use of Dependency Injection, and avoiding the creation of many redundant adaptor types.
- Providing serialized access to resources which are not intrinsically thread-safe, such as sockets and files
- Round-Robin dispatch and predicate-based routing of events and requests to multiple handlers
- Serving as an easy wiring mechanism for loosely-coupled and plugin-based applications
- Serving as an intermediate step for monolithic applications which are in the process of being decomposed into microservices
- Serving as an easy bridge for external resources, queues, message brokers and Enterprise Service Buses
- Acting as the communication mechanism for domain events in a Domain-Driven solution
- Simplify the distribution of work among many threads while avoiding many common pitfalls of multi-threaded programming
Additional Features
Acquaintance provides several features which are extensions of the three basic messaging patterns above:
- Built-In Unit testing functionality with ability to mock channels and expect messages
- Message timer to generate application heartbeats on a set schedule
- Automatic polling of asynchronous event sources
- Automatic delivery retry with the Outbox pattern
Design Goals
Acquaintance follows several design goals and principles:
- Make good use of appropriate patterns, including Gang-of-Four, Messaging and Architectural patterns
- Be lockless and non-blocking
- Optimize for the common case, both in terms of performance and usability
- Use good, fluent interfaces to help features be usable and discoverable
- Be flexible and extensible to support a wide variety of projects and use-cases
Contraindications
There are situations where using Acquaintance is appropriate and situations where it is not. See the page on Contraindications for a discussion of these.