A typical SOA implementation relies on multiple services. Invoking these services requires knowledge of their location (i.e., the service endpoint address) and binding (transport mechanisms for reaching this endpoint). In the simplest case it is possible to hard-code endpoint addresses in implementations. This approach introduces a tight coupling between the solution's implementation and the service locations (location coupling). An implementation can improve this situation through externalizing the endpoint addresses into configuration files .NET. This allows a solution to accommodate address changes without any code modifications. However, this option also runs into scalability problems as the numbers of consumers and services (and consequently of configuration files) grow.
Relying on a component specialized in dynamically resolving service queries into endpoint addresses and invocation policies - a service registry - provides a significantly more flexible and maintainable solution for this problem. The service registry, in this case, contains all the information about service deployments, their locations, and the policies associated with invocations at each location.
The notion of the service registry was initially introduced as part of overall Web Services vision, defined the Universal Description, Discovery and Integration (UDDI) registry as a "match maker" (broker) between services consumers and providers. The responsibility of UDDI was viewed as providing dynamic choice of the service producer based on the functionality required by the consumer. Its role was similar to that of the Yellow Pages. Despite the push from multiple vendors and standards bodies UDDI usage for the service match making never took off. The majority of today's UDDI usage is limited to referencing the service WSDL files, which are used while designing the consumer.
A more practical usage of service registry is run time lookup of the service endpoint/binding based on service name (and policies), similar to a service locator pattern [1], widely used, for example, in J2EE implementations. In this case services definitions (interfaces) are available at (consumer) development time and registry usage is limited to the run time resolution of the services endpoint addresses and dynamic binding.
This article describes a .NET implementation of a service registry that can be used to simplify implementation of SOA solutions.
Overall Architecture
The overall architecture of the proposed solution is presented in Figure 1:
Figure 1 Service registry overall architecture
The foundation of this implementation is a services database, containing information about all services in the system, and a registry service, encapsulating this database and providing a set of "standardized " APIs for accessing this information. Although theoretically it is possible for service consumers to obtain this information directly from the database, the introduction of a service serves several major purposes in this case:
- It abstracts from the actual database schema, thus allowing modifying used database, database layout and access methods, transparently to service consumers.
- It minimizes the amount of database connections, thus improving overall database performance and throughput.
- It allows for publishing of changes in the service related information to consumers immediately (not currently implemented) thus eliminating the necessity of polling databases for changes.
Sidebar. Windows Communications Foundation [8]
Windows Communication Foundation (WCF) is a framework for bring together all of the distributed computing approaches (both existing and new ones) into a single unified programming model thus simplifying building and consuming services. This is achieved through a layered architecture, presented in Figure 2:
Figure 2 WCF Architecture
The top-most layer - contracts - supports flexible service definition (typically used by service consumers). These includes:
- Data or message contracts, defining messaging (data) semantics
- Service contracts, defining service invocation semantics
- Policies and binding, defining technical service invocation semantics, including communication transport, etc.
The service runtime layer allows to customize the implementation of the service itself, including:
- Throttling, controlling the amount of messages, processed by a service simultaneously
- Error behavior, controlling what information is delivered to service consumer in case of internal errors
- Metadata support, controlling queriable information about a service.
- Instance support, controlling instantiation of service's implementation for request processing.
- Transaction behavior, controlling transactional semantics of service invocation.
The messaging layer allows customization of message delivery, through message delivery channels. There are two types of channels: transport channels and protocol channels:
Finally, activation and hosting layers supports execution of service's implementation in different environments, including standalone executables, Window's services, IIS, Windows Activation Service (WAS), etc.
- Transport channels support physical message delivery transports, for example HTTP, named pipes, TCP, and MSMQ.
- Protocol channels support implementation of many web services standards, for example WS-Security and WS-Reliability over the basic transport channels.
In addition to the typical service registry implementation that stores only endpoint addresses and binding types, we have extended the content of the service parameters to include additional configuration parameters, for example send/receive timeouts, message sizes, etc. This allows not only to change endpoint addresses and binding types (features found in many registry implementations), but also to tweak binding parameters using a centralized registry.
Usage of registry by service consumers slightly changes service consumer implementation approach. Typically service consumers are implemented using service proxies, which are generated based on the existing service implementation. These proxies have to be maintained as additional artifacts, and modified every time when a service interface changes.
In our implementation a service consumer leverages .NET's ChannelFactory
class, which allows us to dynamically generate a service proxy, based on the service's interface and endpoint and binding. The usage of this approach not only allows us to bypass service proxy generation, but also to use the same interfaces for both service and client implementation. Elimination of the service proxies minimizes the code that needs to be maintained and eliminates the necessity of synchronizing of the interface changes with the distribution of regenerated service proxies. Service consumer and provider implementations share the same service interface definition project, which ensures keeping them in synch.
Finally the role of the registry maintenance application is to support viewing and modifying of the content of the services definition database. It provides capabilities for viewing of list of services and details of each particular service along with support for adding and/or updating of service definitions. Currently this application is used to enter binding/endpoint information about existing services that can be used by service consumers to use these services.
Database design
Figure 3 below shows the WCF service definition and the supported bindings and parameters relevant for each of them:
Figure 3 Service definition
In addition to the Web Services bindings, supported by WCF, the service registry also supports local (language) binding.
Every supported binding type has its own URL format. Additionally we have added a URL format for local bindings. A list of URL formats for supported bindings is summarized in Table 1:
Bindings | URL |
local | "local://assembly/class" |
basicHTTPBinding | "http://localhost/servicemodelsamples/service.svc" |
wsHTTPBinding | "http://localhost/servicemodelsamples/service" |
wsDualHttpBinding | "http://localhost/servicemodelsamples/service" |
wsFederationHttpBinding | "http://localhost/servicemodelsamples/service" |
netTcpBinding | "net.tcp://localhost:9000/servicemodelsamples/service" |
netPeerTcpBinding | "net.p2p://broadcastMesh/servicemodelsamples/announcements" |
netNamedPipeBinding | "net.pipe://localhost/servicemodelsamples/service" |
netMsmqBinding | "net.msmq://localhost/private/ServiceModelSamplesTransactedBatching" |
Table 1 Bindings and corresponding URLs
The database design that supports this information is presented in Figure 4:
Figure 4 Database Design
The whole database schema consists of 4 major tables:
- The Service table represents the basic information for a service. In addition to the service name it contains service category (a namespace for a service name that helps to avoid name collision and provide for better organization of services), service version, allowing for simultaneous deployment of multiple versions of the same service and allowing the consumer to choose the version he wants to use. An interface is a fully qualified name of the service interface, ensuring that a consumer asks for the interface that a service really exposes. Finally, the ConsumerType parameter allows us to assign different service endpoints/bindings to different types of consumers. Such implementations allow, for example, supporting platinum/golden/etc. customer types and providing different SLAs (endpoins/bindings) for them.
- The Binding table supports the majority of the information for bindings, including their type and corresponding parameters. This table represents a superset of all parameters for all types of supported binding. The only two exceptions from this are reliability and security parameters, which are separated into two additional tables.
- The Reliability table supports parameters, required for specifying of support for reliable messaging.
- The Security table (not currently fully implemented) support a set of parameters required to support secure services communications.
Ideally, the service registry implementation should support reuse of reliability/security data by bindings and binding by services. We are not supporting this yet. At the moment, implementation uses one-to-one dependencies between services and bindings and bindings and reliability/security. This leads to data duplication in the database, but is significantly simpler to implement and maintain.
In addition to these tables, we are using a view (Figure 5), allowing us to simplify database access for getting a complete service definition.
Figure 5 Complete service join
Service implementation
For service design we decided to use Web Services Software Factory (WSSF) modeling edition. Using this factory, the following data types used for the service definition were designed (Figure 6).
Figure 6 Service Registry Data types
Service registry contracts, designed using WSSF are presented at Figure 7:
Figure 7 Service Registry Contracts
The actual implementation of the service is fairly simple and straightforward. Its foundation is a persistent layer, implemented using Microsoft Patterns and practices enterprise library (data access block) and repository pattern [2], providing a dependency-free access to data of any type. The implementation consists of the following classes (Figure 8):
Figure 8 Persistence layer
- Repository (generic). This generic repository base class sends and retrieves specific domain objects by using various factory classes. The factory classes use ADO.NET DbCommand objects and execute them against the database using the Enterprise Library Data Access Application Block.
- ServiceRegistryRepository. This is a specific implementation of the Repository class for Service Registry, which provides the primary interface to the data access layer.
- IDomainObjectFactory(generic). This interface specifies the signature for a factory that takes a DataReader type and creates a domain object from it.
- ServiceObjectFactory(entity-specific). This implementation of the IDomainObjectFactory interface can turn a returned result set into a service object.
- IdentityObject(concept). The IdentityObject identifies which entity or entities a search is looking for. There is no specific IdentityObject class or interface, because the actual type of the IdentityObject varies depending on the search.
- ISelectionFactory(generic). This interface provides the signature for the class that converts an IdentityObject type into a DbCommand command to perform a search for the corresponding result set.
- ServiceRegistrySelectFactory. This factory is specific to a Service Registry that accepts an identity object (Service Query) and returns an appropriate DbCommand, which is able to return Service result set.
- IInsertFactory(generic). This interface-to-factory class generates a DbCommand to insert a new domain object into the database.
- ServiceRegistryInsertFactory(operation-specific). This factory is specific to a Service Registry insert operation; it generates a DbCommand to insert a new service object into the database.
- IUpdateFactory (generic). This interface-to-factory class generates a DbCommand to modify a domain object in the database.
- OperationUpdateFactory (operation-specific). This factory is specific to a single operation (for example, UpdateCustomer); it generates an appropriate DbCommand command to modify a domain object in the database.
- IDeleteFactory (generic). This interface-to-factory class generates a DbCommand to delete a domain object from the database.
- ServiceRegistryDeleteFactory (operation-specific). This factory is specific to a Service Registry delete operation; it generates an appropriate DbCommand command to delete a Service object from the database.
- IDataReader. This is defined in the System.Data namespace. This interface provides a means of reading one or more forward-only streams of result sets obtained by executing a command at a data source and is implemented by the .NET Framework data providers that access relational databases.
The Service implementation class uses the data access layer directly (ServiceRegistryRepository class) for execution of service. Once a specific request is received, service implementation invokes an appropriate method on a ServiceRegistryRepository to execute an appropriate database operation and return results back.
Service consumer support
The primary users of the service registry service are service consumers, accessing business services. The introduction of a service registry reinforces dependency injection patterns [3,4] for service consumer implementations - service consumers depend on a particular interface (such approach allows to completely hide WSDL/XSD of service from its consumer. They still play a significant role in the service design, but consumer implementation is based solely on service interface, thus completely hiding all distribution and access mechanisms), where a specific implementation (and binding) are injected at the runtime through the registry access, encapsulated in a proxy builder class that is provided as a part of the overall implementation.
In order to improve overall performance, a service consumer caches endpoint information. We are currently using configurable time-based caching strategy; a more advanced publish-subscribe cashing strategy will be introduced in one of the next versions. A client implementation can dynamically create a proxy for a given interface through usage of a method:
public static Interface getProxy<Interface> (string sName, string sVersion, string sConsumer, string callBackCl)
This method take service name, version, consumer type (generic QoS parameter) and callback interface name (optional parameter) and creates a service proxy for a given interface type. This method first tries to resolve the required proxy in a local cache (see above) and if it is found there immediately returns it back to the invoker. If the proxy is not found locally, the registry service is invoked to find service information based on the input parameters. A fully qualified interface name is used as an additional search parameter in order to ensure that a reference to service implement an interface, required by a consumer 1.
As we have mentioned above, proxy creation in this implementation is based on usage of the ChannelFactory<Interface>
provided by .NET 3.0. This class allows creating a service proxy based on the interface and endpoint address and binding, which are built using information provided by the registry service. Once a proxy is build, it is stored in the cache for further usage and is returned back to user 2.
In addition to connecting to services using WCF support, implementation also supports "local" binding - service implementation that is located in an assembly directly accessible by the client implementation. This can be either an assembly loaded as part of the client implementation or an assembly located in GAC.
ProxyBuilder usage simplifications
We can further simplify usage of the ProxyBuilder implementation by moving service parameters from the code to a configuration file (see example at Listing 1). This approach is modeled after the SCA configuration [5] and allows to further simplify service consumer maintenance.
<configSections> <section name="composite" type="ProxyBuilder.CompositeSectionHandler,
ProxyBuilder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </configSections> <composite> <reference Name = "Calculator"> <Service Name = "TestService" Version = "02" ConsumerType = ""></Service> <Callback Name = "Test.test"></Callback> </reference> </composite>
Listing 1 Reference Configuration
Additionally, the location of registry itself can be moved to the configuration (Listing 2).
<registry> <runtime Location ="http://localhost:58934/WCFServiceRegistry.Host/ServiceRegistry.svc"/> </registry>
Listing 2 Registry Location Configuration
Service Registry maintenance application
This is a separate application, allowing for maintenance of service registry information. It supports the following major functionality:
- Searching and viewing of services information
- Updating service information
- Adding service information
- Deleting services
This application is build using a Master-Details pattern, where the master is basic service information and binding type (contained in a service table) and details contain binding details for a binding type used by a service (Figure 9). These details, and consequently the secondary screen are dependent on the binding type.
The implementation of the main view is based on DataGridView control, which natively provides most of the required functionality, including showing existing record and modification, adding and removing of records. Binding details are implemented as custom user controls. Because details are different, depending on the binding type, we have implemented different forms for different binding types. We used the factory pattern (see sidebar) for creating and populating binding details for a specific binding type.
Figure 9 Service maintenance screen
Sidebar: Implementing Factory Pattern using C# generics
Factory pattern is a common pattern for creation of polymorphic objects of different concrete types. Usage of C# generics allows to significantly simplifying this implementation (Listing 3).
namespace RegistryMaintanence.controls { public interface IControlsFactoryInterface { UserControl createControl(Service service, ServiceMaintanence parent, bool update); } public class ControlsFactory<T> : IControlsFactoryInterface where T : UserControl, InitializableControl, new() { #region IControlsFactoryInterface Members public UserControl createControl(Service service, ServiceMaintanence parent, bool update) { T t = new T(); t.initialize(service, parent, update); return t; } #endregion } public class ControlsFactory{ private static Dictionary<BindingTypeEnum, IControlsFactoryInterface> factories = null; private ControlsFactory(){} private static Dictionary<BindingTypeEnum, IControlsFactoryInterface> getFactories(){ if(factories == null){ factories = new Dictionary<BindingTypeEnum,IControlsFactoryInterface>(); factories.Add(BindingTypeEnum.basicHTTPBinding, new ControlsFactory<BasicHTTPBinding>()); factories.Add(BindingTypeEnum.netMsmqBinding, new ControlsFactory<netMsmqBinding>()); factories.Add(BindingTypeEnum.netNamedPipeBinding, new ControlsFactory<netNamedPipeBinding>()); factories.Add(BindingTypeEnum.netPeerTcpBinding, new ControlsFactory<netPeerTcpBinding>()); factories.Add(BindingTypeEnum.netTcpBinding, new ControlsFactory<NetTcpBinding>()); factories.Add(BindingTypeEnum.wsDualHttpBinding, new ControlsFactory<WSDualHttpBinding>()); factories.Add(BindingTypeEnum.wsFederationHttpBinding, new ControlsFactory<WSHTTPBinding>()); factories.Add(BindingTypeEnum.wsHTTPBinding, new ControlsFactory<WSHTTPBinding>()); } return factories; } public static UserControl createControl(BindingTypeEnum type ,Service service, ServiceMaintanence parent, bool update) { Dictionary<BindingTypeEnum, IControlsFactoryInterface> builders = getFactories(); IControlsFactoryInterface builder = builders[type]; return builder.createControl(service, parent, update); } } }Listing 3 Implementing Factory using generics
In this implementation
IControlsFactoryInterface
interface defines the interface, supported by specific factory. All of the specific factories are implemented using generics -ControlsFactory<T>
class. This class relies on C# generics support for constraints [7], that allow to specify which constraints the client-specified types must obey. Usage of constraints allows a specific factory to invoke operations, defined on a base class.Finally, the generic factory implementation is based on a dictionary, allowing for picking an appropriate specific factory based on a type of binding.
Future enhancements
There are several main enhancements that we are planning for a future implementation
Implementing Pub/Sub for services update
The current implementation supports expiring of the service endpoint information after a predefined time interval. This means that it periodically polls the service registry application to ensure that cached service information is current. This strategy:
- Creates additional network - it reads service information regardless whether any changes occurred.
- Creates a window of potential mismatch between actual and cached service information.
As a result, service proxy configuration becomes a balancing act between optimizing performance (minimizing network traffic), requiring to increase cache update intervals and minimizing of the service information mismatch, requiring to decrease cache update intervals.
A better solution to this problem is a pub/sub implementation (see for example [6]). In this case proxy builder, while getting service information about particular service, subscribes to the updates of this service information, which are published to it by a registry service.
Extending maintenance application
As we have mentioned above the role of maintenance application is to simplify creation and modification of services information. As such this application is geared towards support personnel. A common question that is asked from support personnel is which services are operational at any given point. Introduction of the service registry as a centralized place for all of the services location information can simplify answering this question - based on the knowledge of all existing services and their locations, the maintenance application can "ping" services implementation and display their current status. Such an implementation requires not only additional functionality incorporated into maintenance application, but also "ping" support designed from ground up in services implementations. The simplest way to implement it is to derive all of the service interfaces from a standard "ping" interface (Listing 4), thus forcing its implementation by every service.
namespace Service.Instrumentation.Management { // Define a service contract. [ServiceContract(Namespace = "http://Service.Instrumentation.Management")] public interface IPing { [OperationContract] bool Ping(); } }
Listing 4 Ping Interface
Once this is done, incorporation of service verification functionality into registry maintenance application is fairly straightforward.
Conclusion
As described in [9], service registries and repositories are the linchpin of SOA implementations. Commercial implementations (with a very few exceptions) are not used as purely run-time repositories. Besides, use of commercial products can be overly expensive (licenses, customization, training, deployment costs) for small and medium size projects. This article presented a fairly simple implementation of registry (it took less then 3 weeks from beginning of design to implementation completion), which supports the majority of required SOA run time registry functionality. It also supports some additional features that are not found in commercial products.
Acknowledgements
Author is thankful to his colleagues, especially Paul Rovkah and Robert Sheldon, for multiple discussions, that led to improvements of both implementation and this article.
About the author
Boris Lublinsky has over 25 years experience in software engineering and technical architecture. For the last several years he focused on Enterprise Architecture, SOA and Process Management. Throughout his career, Dr. Lublinsky has been a frequent technical speaker and author. He has over 50 technical publications in different magazines, including Avtomatika i telemechanica, IEEE Transactions on Automatic Control, Distributed Computing, Nuclear Instruments and Methods, Java Developer's Journal, XML Journal, Web Services Journal, JavaPro Journal, Enterprise Architect Journal and EAI Journal. Currently Dr. Lublinsky is Principal Architect with HerzumSoftware where his responsibilities include developing Software Factories and help customers to implement them.
References
1. Core J2EE Patterns - Service Locator.
2. Edward Hieatt and Rob Mee. Repository.
3. Martin Fowler. Inversion of Control Containers and the Dependency Injection pattern. January 2004.
4. Griffin Caprio. Design Patterns: Dependency Injection. September 2005.
5. Service Component Architecture Specifications.
6. Juval Lowy. What You Need To Know About One-Way Calls, Callbacks, And Events.
7. Juval Lowy. An Introduction to C# Generics.
8. Windows Communication Foundation Architecture.
9. B. Lublinsky. Explore the role of service repositories and registries in Service-Oriented Architecture (SOA) IBMDeveloperWorks, May 2007.