Raven integration · Preview
Raven and MyServiceBus.
MyServiceBus integrates naturally with Raven as a .NET language. Raven applications can consume the same runtime and contracts while using more expressive forms where the language has stronger primitives—from namespace-level consumer functions and unions to an experimental compiler-backed saga DSL.
Raven and MyServiceBus are both evolving. Ordinary CLR integration is the foundation, while consumer-function projection, .NET 11 unions, generated registration, and the saga! macro have capability-specific preview or experimental boundaries. Do not treat this page as a stable 1.0 API commitment.
Start MyServiceBus with RabbitMQ
Reference the preview Sundstrom.MyServiceBus.RabbitMq package from a Raven project, start a local RabbitMQ broker, and configure the bus through the same .NET hosting and dependency-injection APIs used by C#. The complete minimal application below discovers its attributed consumer function, creates the submit-order receive endpoint, and starts the bus as a hosted service.
The consumer publishes OrderSubmitted through its active consume context, so correlation, retry, fault, and settlement behavior remain attached to the delivery. Replace localhost or add credentials through the normal RabbitMQ configurator when the broker is remote.
Use the getting-started guide for the tested Docker command and current package version. Raven project files use ordinary NuGet PackageReference entries.
Express the same messaging concepts in Raven
MyServiceBus does not require Raven to imitate C# source structure. A consumer is fundamentally a callable declaration with a message, optional consume context, application services, and an endpoint mapping. Raven can express that naturally as an attributed namespace-level function rather than adding a class only to contain one method.
The function shape remains local to the application. MyServiceBus binds it to the ordinary consumer pipeline, where endpoint topology, scoped services, retries, faults, telemetry, and message settlement retain their normal meaning. On the wire, SubmitOrder is the same contract a C# or Java service sees.
How a consumer function binds
Raven compiles a namespace-level function into an attributed CLR method. Preview reflection discovery scans the supplied assembly and builds the same method-consumer descriptor it would build for an attributed C# method; the compiler-generated containing type is not part of the consumer model.
Endpoint
[Consumer("orders")] gives the function an explicit receive endpoint. Functions using the same endpoint contribute bindings to that endpoint.
Message
The first ordinary parameter identifies the consumed contract. The function name is organizational and does not select the message.
Context
Add ConsumeContext<T> or a cancellation token only when the handler needs receive metadata or cancellation.
Services
Additional ordinary parameters resolve from the dependency-injection scope created for that delivery.
One-way functions may complete synchronously or asynchronously. A request consumer can return its response asynchronously, and MyServiceBus sends it through the active consume context with the original request correlation. Exceptions still enter the configured retry and fault pipeline.
Today this Raven path uses assembly reflection. The C# source generator cannot see Raven source, so a trimmed or NativeAOT Raven application will eventually need a Raven-generated consumer catalog or another shared descriptor manifest. That is a tooling boundary, not a different runtime or wire model.
Raven's union syntax provides another expressive projection. For a one-off handler, the message parameter can use the ad hoc type SubmitOrder | CancelOrder directly. When that closed protocol has a durable domain meaning or appears in more than one place, give it a name with a parenthesized union declaration: union OrderCommand(SubmitOrder | CancelOrder).
The named union makes the accepted command family visible and reusable without adding an artificial base message class. The packaged smoke test executes this namespace-level function for both concrete messages, verifies that they share one endpoint, and confirms that OrderCommand never enters message topology. MyServiceBus registers and transports SubmitOrder and CancelOrder independently. See the .NET 11 union experiment for the current ABI and AOT boundaries.
Deconstruct records and match unions
Raven records participate directly in deconstruction. For one known message type, let (orderId) = message binds its positional record value without repeating the property access. Named elements can be used when position would be unclear.
For a union-shaped consumer, match performs both operations at once. The SubmitOrder(let orderId) arm proves which concrete message arrived and deconstructs that record's payload. Because OrderCommand is closed, Raven can check that the function handles every alternative. Adding another union member therefore makes the missing consumer behavior visible at compile time instead of silently falling through a base-type handler.
This is where the Raven projection becomes more than shorter punctuation: message alternatives, exhaustive handling, and payload binding are expressed directly in the function's type and patterns, while MyServiceBus continues to route the concrete contracts.
Use .NET directly
Reference MyServiceBus assemblies and ordinary CLR message contracts from Raven.
Prefer functions
Declare concise namespace-level consumers where a container type adds no meaning.
Use language types
Express alternatives with Raven unions while preserving concrete portable message identities.
Build domain DSLs
Use Raven macros to add precise workflow syntax over the existing MyServiceBus runtime.
Describe a saga as a state machine
The macro generates an ordinary saga-data type and a native SagaStateMachine<TSaga>. The resulting definition executes through the same MyServiceBus correlation, repository, activity, transition, finalization, topology, and monitoring boundaries used by a C# state machine.
That separation matters. Raven is a higher-level authoring projection, not another orchestration engine. Java services do not need Raven, and messages exchanged with Java or MassTransit do not acquire Raven-specific envelopes, headers, addresses, or contract identities.
The executable sample creates an order saga, correlates three messages, sends inventory and payment commands, publishes completion, transitions through two states, and finalizes the instance. Assertions run against the real in-memory MyServiceBus saga repository and runtime.
It lives under test/Experiments/RavenSagaDsl, uses Raven 0.1.6, and references the MyServiceBus source project. It is executable evidence for the integration design, not yet a supported package or stable syntax.
Make every position mean one thing
The macro body is a precise domain language, not an unrestricted Raven block. Each authored position has a category:
onaccepts and completes correlated message declarations.inandtransitionaccept and complete states declared by the saga.setaccepts and completes persisted saga-data members.- A behavior accepts only supported activities such as
set,send,publish,transition, andfinalize. - Correlation positions distinguish a message type from the saga member used as its identity.
Raven language fragments begin only where the DSL explicitly asks for a value. The right-hand side of set and the outgoing message expression after send or publish use ordinary Raven parsing and language services. The handler alias—message above—is introduced with its correlated message type, enabling member completion, hover, navigation, and diagnostics inside those expressions.
The DSL still owns the surrounding intent. A valid Raven expression cannot invent an undeclared state, use an uncorrelated event, or add an unsupported saga activity.
Understand the current boundary
The prototype covers generated data, identity correlation, initial and state-specific behavior, mutation, send, publish, transition, and finalization. It deliberately diagnoses semantics that the current portable saga profile has not settled rather than approximating them.
Named final outcomes, guarded transitions, durable after scheduling, requests, composite events, durable sample persistence, and generated package integration remain future work. The sample's convention-based queue:MessageType send destination is also temporary; a product DSL needs an explicit or topology-resolved destination model.
One integration, explicit maturity
The design principle is stable even where an individual feature is not: MyServiceBus owns messaging behavior and portable contracts; Raven supplies an idiomatic source projection. C# and Java remain complete alternatives, and no service needs Raven merely because another participant uses it.
Consumer functions, unions, and saga declarations can mature independently. Each feature should state whether it uses reflection, generated descriptors, a preview ABI, or a repository-only macro so teams can evaluate it without mistaking the overall integration story for a single support level.
Continue with workflow coordination fundamentals or see how declared workflows and saga runs appear in runtime monitoring. Raven itself is documented on the Raven website.