Although it is technically possible to use the same service code both connected and queued (with simple changes such as configuring operations as one-way, or adding another contract for the one-way operations), in reality it is unlikely that you will actually use the same service both ways. The reasons are similar to the arguments made in the context of asynchronous calls, discussed in Chapter 8. Synchronous calls and asynchronous calls addressing the same business scenario often have to use different workflows, and these differences will necessitate changes to the service code to adapt it for each case. The use of queued calls adds yet another barrier for using the same service code (both connected and disconnected): changes to the transactional semantics of the service.
Consider, for example, Figure 9-10, which depicts an online store application that uses connected calls only.
The Store
service uses three well-factored helper
services to process the order: Order
, Shipment
, and Billing
. In the
connected scenario, the Store
service calls the Order
service to place the order. Only if the Order
service succeeds in processing the order (that is, if the
item is available in the inventory) does the Store
service call the Shipment
service, and only if the
Shipment
service succeeds does the Store
service access the Billing
service to bill the customer. The connected case involves exactly one
transaction created by the client, and all operations commit or abort as one atomic
operation. Now, suppose the Billing
service also exposes
a queued endpoint for the use of the Store
service, as
shown in Figure 9-11.
The queued call to the Billing
service will be played
to the service in a separate transaction from that of the rest of the store, and it could
commit or abort separately from the transaction that groups Order
and Shipment
. This, in turn, could
jeopardize the system's consistency, so you must include some logic in the Billing
service to detect the failure of the other service and
to initiate some compensating logic in the event that it fails to do its work. As a result,
the Billing
service will no longer be the same service
used in the connected case.
Since not every service can be connected and queued, and since some services may be
designed for a particular option and only that option, WCF lets you constrain a service's
communication pattern. The DeliveryRequirements
attribute presented in Chapter 1 also lets you insist on queued or
connected delivery of messages to the service:
public enum QueuedDeliveryRequirementsMode { Allowed, Required, NotAllowed } [AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class, AllowMultiple = true)] public sealed class DeliveryRequirementsAttribute : Attribute,... { public QueuedDeliveryRequirementsMode QueuedDeliveryRequirements {get;set;} public bool RequireOrderedDelivery {get;set;} public Type TargetContract {get;set;} }
This attribute can be used to constrain a contract (and all its supporting endpoints)
or a particular service type. The default value of the QueuedDeliveryRequirements
property is QueuedDeliveryRequirementsMode.Allowed
, so these definitions are
equivalent:
[ServiceContract]
interface IMyContract
{...}
[ServiceContract]
[DeliveryRequirements]
interface IMyContract
{...}
[ServiceContract]
[DeliveryRequirements(QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Allowed
)]
interface IMyContract
{...}
QueuedDeliveryRequirementsMode.Allowed
grants
permission for using the contract or the service with either connected or queued calls.
QueuedDeliveryRequirementsMode.NotAllowed
explicitly
disallows the use of the MSMQ binding, so all calls on the endpoint must be connected
calls. Use this value when the contract or the service is explicitly designed to be used
in a connected fashion only. QueuedDeliveryRequirementsMode.Required
is the opposite: it mandates the use
of the MSMQ binding on the endpoint, and it should be used when the contract or the
service is designed from the ground up to be queued.
Even though the DeliveryRequirements
attribute
offers the RequireOrderedDelivery
property (discussed
in Chapter 1), if QueuedDeliveryRequirementsMode.Required
is used, then RequireOrderedDelivery
must be false
, because queued calls inherently are unordered and messages may be
played back in any order.
When the DeliveryRequirements
attribute is applied
on an interface, it affects all services that expose endpoints with that contract:
[ServiceContract] [DeliveryRequirements(QueuedDeliveryRequirements = QueuedDeliveryRequirementsMode.Required)] interface IMyQueuedContract {...}
The client as well can apply the DeliveryRequirements
attribute on its copy of the service contract.
When the DeliveryRequirements
attribute is applied
on a service class, it affects all endpoints of that service:
[DeliveryRequirements(QueuedDeliveryRequirements = QueuedDeliveryRequirementsMode.Required)] class MyQueuedService : IMyQueuedContract,IMyOtherContract {...}
When applied on a service class while using the TargetContract
property, the attribute affects all endpoints of the service
that expose the specified contract:
[DeliveryRequirements(TargetContract = typeof(IMyQueuedContract)
,
QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Required)]
class MyService : IMyQueuedContract,IMyOtherContract
{...}
3.135.213.212