Building services in a loosely coupling manner.

Service Architecture

Building services in a loosely coupling manner is our main goal. The less intimate two or more parties are the more tolerant they are towards changes.
The general architecture for a Service is depicted here:

Building services fig 1

Implementing a Service

When implementing the architecture depictured in Fig 1, we follow these general steps:

  1. Define Message Contract.
  2. Define Data Contract.
  3. Optional define a Fault Contract.
  4. Define Service Contract Interface.
  5. Define Service Implementation and Implement the Service Contract from step 3.
  6. Define Business objects
  7. Connect Business object to the Service Implementation


The Service layer:

The Service layer consists of 5 artifacts:

  • Service Contract
  • Message Contract
  • Data Contract
  • Fault Contract
  • Service Implementation

The Service Contract defines what operations a service can perform. The Message Contract defines the service messages our service can send and receive. The Message contract carries the Data Contract as payload. The Data Contract is often based on the entity model defined from a domain model but this is not a requirement. The Fault contract is optional but provides us with the opportunity to define custom Soap faults to a client using our service.

The service adapter implements the service contract that is exposed on an endpoint (commonly referred to as the service host) and is responsible for adapting the endpoint to the underlying business layer. The Service adapter is a key artifact that enables us to decouple our implementation from the messaging. It is also a good place to perform message transformations if they are required.

Service Contract

From a developers view a Service Contract is expressed in terms of an interface. The following interface shows a service contract that defines the operations a client can perform on the IPizzaOrderingService contract.

[ServiceContract(Namespace="http://company.com/services/pizza/2007/01")]
public interface IPizzaOrderingService
{
[OperationContract]
ResponseOrderMessage OrderPizza(OrderMessageRequest pizzaOrder);

[OperationContract]
void CancelPizza(OrderMessage cancelOrder); }

Message Contract

A Message Contract holds definitions of the messages required to communicate with the service, which includes inbound and outbound messages.
The following defines a message contract for the IPizzaOrderingService:

[DataContract(Namespace="http://messagecontract.pizza/2007/01/OrderMessage")]
public class OrderMessageRequest
{
[DataContract]
public Order PizzaOrder;
}

This inbound message called OrderMessageRequest is designed using code, but the message could equally have designed as a schema (XSD) in notepad and then afterwards have been generated to the above OrderMessageRequest. The generation of schema to code is done by your tool support like xsd.exe or svcutil.exe.

The important part here in terms of what defines the message is the OrderMessageRequest it self and not the payload “Order” which is called the Data Contract.

Data Contract

A data Contract holds the data definitions that are obtained from the domain model.
Here is the definition of the Order data contract. The Order data contract is the payload of the OrderMessage

[DataContract(Namespace = http://datacontract.pizza/2007/01/Order)]
public class Order
{
[DataMember]
public int NumberOfPizza;
[DataMember]
public int PizzaNumber;
[DataMember]
public string ExtraFilling;
}

As with the Message Contract we put the Order data contract in a separate namespace for versioning reasons.
The Order data contract has now knowledge of the OrderMessageRequest message contract.

Service Implementation

The initial implementation of the Service Contract is listed here.

public class PizzaOrderingService : IPizzaOrderingService
{
#region IPizzaOrderingService Members
public ResponseOrderMessage OrderPizza(OrderMessageRequest pizzaOrder)
{
// call business object with pizzaOrder.Order as argument
// business object returns a PizzaConfirmation which is then attached // as payload
// to the ResponseOrderMessage.PizzaConfirmation.
}

public void CancelPizza(OrderMessageRequest cancelOrder)
{
//call business object with cancelOrder.Order as argument
}
#endregion
}

We have yet not attached any business objects to the Service implementation. This will be the next step in building our service.

Business layer

The business layer consists of 1-2 artifacts:

  • Business Entity.
  • Business Object

The Business entities are classes that model the domain specific objects. Business entities can be simple objects that maps directly to the payload of the Message Contract or they can take form of more complex objects perhaps partial mapped to a Data Contract.

The business objects implements the actual business behavior. Business objects operate on the business entities and Data Contracts to perform a desired action. The business objects operates on the underlying Data access layer, which provides access to data access logic.

Business objects:

Let us define a business object called PizzaBusiness. As we notice the business objects operates on the data contract and not the message contract. We could have defined an extended business entity of the Order data contract, but in this example we settle for the Order data contract to act as our entity.

public class PizzaBusiness
{
public PizzaConfirmation HandleOrder(Order order)
{
if (order != null)
{
//-perform extra validation of inbound order
//-contact data-access layer for placing the order to storage.
//-return a confirmation
}
PizzaConfirmation confirmation = new PizzaConfirmation();
confirmation.PickupTime = DateTime.Now.AddMinutes(15);
confirmation.ReservationNumber = GetNextTicketNumber();
confirmation.TotalAmount = CalculatePrice(order.NumberOfPizza, order.PizzaNumber, order.ExtraFilling);

return confirmation;
}
.....
}
Looking at the business object, we see no notation of the message contract, service interface and service implementation. This level of indirection is flexible and gives us the opportunity to change the Service implementation without affecting the core business layer.

The design here has a nice side effect in regards to testing our business objects. Armed with only the data contract and the business object, you can perform a very isolated unit test.

Connecting the Business object to the Service implementation

Finally we hook our business object up to the Service implementation as shown here:

public class PizzaOrderingService : IPizzaOrderingService
{
#region IPizzaOrderingService Members
public ResponseOrderMessage OrderPizza(OrderMessageRequest pizzaOrder)
{
ResponseOrderMessage res = new ResponseOrderMessage();

PizzaBusiness bu = new PizzaBusiness();
res.Confirmation = bu.HandleOrder(pizzaOrder.PizzaOrder);
return res;
}

public void CancelPizza(OrderMessage cancelOrder)
{
//call business object with cancelOrder.Order as argument
}
#endregion
}

As we can see, the Service implementation now delegates the payload of the message contract to the business object, which in return will hand us a confirmation object. This confirmation object is then attached the response message and returned to the caller.

 

Building services fig 2

 

Fig. 2: shows a detailed view of the architecture

Mapping layers to projects in Microsoft Visual Studio.NET.

A word on how to organize and divide the architecture into projects in Visual Studio is depictured here.

Building services fig 3


Each color represents a project in Visual Studio.

The general architecture makes no assumption about how and where it is hosted nor have we made any direct assumption about the technology for hosting a Service. The way we have designed our service makes us agile in regards to technologies like Windows Communication Foundation (WCF) and ASP.NET Web Services.

If we want to expose our Service, we configure the Service Implementation in WCF and if we need to expose our Service as an ASP.NET service, we aggregate the Service Implementation in an ASP.NET Web Service.

References:

http://msdn.microsoft.com/en-us/library/bb245673.aspx