Services and the [DEFAULT] contract can be very confusing. In the post I'll show you why that is and some simple ways to resolve the problems in your mind. Then we'll look at how to model send-only and receive-only services, which is another constraint you can use in your SSB design.
This is another post in my Service Broker Demystified series. Services and [DEFAULT] contracts can be a bit confusing. Let's see if we can clear things up. I already covered the basics of Services and Contracts here and here. If you want to follow along with the examples for this post you can download the repro script here.
A bit of trivia first...what is the difference between ServiceA and ServiceB in the screenshot below? You may be looking at that and thinking, "how can two services point to the same queue?" It's perfectly legal. We'll cover some (terrible) use cases for that below. For now I'm merely interested in understanding what
[DEFAULT] is going to do to ServiceB to make it work differently from ServiceA.
In previous posts we noted that the [DEFAULT] contract is not the default contract on a service. But what does that mean regarding functionality?
I can tell just by looking at these services that ServiceA is an initiator-only service and ServiceB might be an initiator or a target service.
[DEFAULT] tells me that.
Let's take a step back. An SSB Service can be thought of as a set of tasks that includes where to route and queue a message, any contracts that may be enforced, and whether or not an "activator" is fired when a message is enqueued. The "
[DEFAULT]" listing indicates ServiceB enforces the
[DEFAULT] is not the same as DEFAULT. The latter is a keyword in SQL Server that indicates a value that should be used when no value isspecified. The former is the name of a contract in SSB that has very distinct characteristics. Specifically it uses validation of NONE...meaning any value is allowed.
Modeling Send-only Services
So, how do I know ServiceA is an initiator only? Initiator services are not bound to a contract. Why? Initiator services are allowed to send to potentially many target services, each with its own set of possible contracts. Let's prove that ServiceA is a send-only service and ServiceB can either send or receive. You can download the repro script from this link.
Here we send a message from ServiceA to ServiceB. Note that the message reaches the destination properly and without error.
Since QueueA is shared between two services it's a bit difficult to know that in fact the single message in QueueA is the success message vs a failed delivery message from ServiceA. In this case we also interrogate
sys.transmission_queue to prove everything worked.
Let's see what happens if we attempt to send from ServiceB to ServiceA. We know that this should fail because ServiceA is not bound to any contracts, not even
...and here we can clearly see that QueueA now contains the Error as well as the Success! message from the last experiment. Note that
sys.transmission_queue logged the error as well...
...if we expand the XML error message we see exactly why ServiceA cannot be a target...
So you create a send-only service by not binding anycontracts to the service declaration.
Modeling Receive-only services
We proved that it is possible to model "send-only" services. Having services with the ability to only initiate a dialog means that we have another check to ensure no one writes code that is accidentally starting dialogs to services that will never process the messages. Can we create receive-only services to ensure that developers don't accidentally send from a service where that was never the intention?
Let's try (you can follow along with the repro script).
Here we created a ReceiverSvc bound to ReceiveOnlyContract. The intent was to force ReceiverSvc to NOT be able to initiate any dialogs. Don't get caught up in how I did this because the fact is, nothing will prevent a server from initiating a dialog if someone attempts to do that and has the necessary authorization. Your odds also improve if you do NOT specify a contract on the dialog or a message type on the SEND statement:
Note that this does work:
There is no possible way to ensure that your receive-only services do not accidentally start dialogs due to programmer error. You simply need to "handle" these cases in your activator procedure on your receive-only services.
Don't create God Objects in your SSB designs
I've seen some SSB implementations where there is one "master service" that performs lots of little tasks. I think this is bad design. Essentially you differentiate what each of these little tasks does by using different Contracts and Message Types.
In this example (follow along with the repro script) there is one big 'ol EnterpriseSvc that services one big GenericQ. This is an example of the "God Object" anti-pattern. There are lots of contracts and message types that will handle doing the heavy lifting, but all embedded in one giant "class". Assuming GenericQ was an activated queue you would process your queue by dequeueing messages in the activator and then determining the business logic based on the Message Type (which I omitted in the screenshot). This pattern has scalability issues baked-in. If a given contract/message type begins to overwhelm the service there is no way to segregate that service on a different instance or processing schedule.
I went down a tangent a bit there. The point of this post is that EnterpriseSvc, as declared, can be an Initiator or a Target. When possible, I like to model my services such that they are either Initiators or Targets, but not both. Then I don't need to worry about a codepath accidentally sending a message to a service that should be only an Initiator. I view Initiator services as nothing more than pieces of code that want to invoke a workflow (generally from a stored proc). But I don't want to accidentally have an Initiator receive a message. Unfortunately there is no way to do this.
In this post we covered the differences between send and receive services. Send-only (initiator) services have no contract bound to them. Send-and-receive services have at least one contract bound to them which can minimally be [DEFAULT]. The key takeaway is that an iniitating service does not require a contract to be specified, which makes it only able to receive messages in response to a conversation that was initiated from it. There is no way (that I've ever found) to model true, receive-only services.
sql server service broker service broker demystified