Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Current »

The Service Container allows the usage of the Dependency Injection Pattern through a container for classes that another class  depends on. Services are the implementation of the dependency of a class. The terms Services and Service Container have been adopted from the .NET Visual Designer implementation (http://msdn.microsoft.com/en-us/library/system.componentmodel.design.servicecontainer%28v=vs.110%29.aspx). In the .NET Visual Designer the design canvas, the property grid or the name generator for new control instances are services required to implement the whole designer functionality. The service container decouples the components in the whole system from each other.

When building systems of dependent classes the service container solves the problem of accessing central (to the session or an application context, e.g. a Form) objects providing functionality to other objects.  The service container allows object instances to be registered and returns them on request from other classes. The “key” for requesting object instances is typically an interface that the requested class (the dependency) implements and thus the “role” it plays for the requestor (the dependent class).

The usage of dependency injection provides a number of benefits when designing large systems:

Systems become easier customizable. Classes do not need to know the exact implementation of a service that they depend on. The actual service implementations may have been registered in the service container way before they are needed or the service container may use a call back to create an instance when first needed. But in any way the class that depends on the service has no knowledge about the actual implementation or about how to create an instance of it.

This provides a huge advantage also when service implementations should be customizable. In this case a custom implementation will be registered in the service container under the service interface and will so become available to the dependent class without the need to change any code there.

Other patterns for providing central functionality such as the singleton pattern or static classes do not allow this customization as the dependent class needs to know exactly into which class to call to resolve the dependency.

As a consequence dependency injection makes unit testing much simpler. In essence the service container and the dependency injection pattern allow the injection of mockup services. Mockup services simulate the behavior of a dependency in a much simpler and thus potentially fault free way. As the result a unit test can be specialized on just testing the dependent bits of code – simplifying the location of the cause for failures compared to debugging a whole complex system.

The SmartComponent Library and the WinKit do heavily rely on dependency injection and the service container to allow customization (with mandatory and optional services).  

Customers may use services and the service container also for application services such as price calculation, stock enquiry or for adapters to other systems.

The Service Container

The Service Container of the SmartComponent Library allows to register service classes under services types (interfaces or classes). The same object instance may be registered multiple interfaces in case it provides more than a single role to the system. The Service Container as a registry does not verify that services do actually implement the interface they are registered under.

The Service Container provides three public methods for the registration, deregistration and accessing of services.

The method AddService adds a service instance to the Service Container (registration). The service will be registered under the given type (instance of Progress.Lang.Class).

AddService syntax
/*------------------------------------------------------------------------------
    Purpose: Adds the specified service to the service container.                                                                     
    Notes:
    @param poClass The reference to the class or interface of the service to add
    @param poObject An instance of the service type to add. This object must implement or inherit from the type indicated by the serviceType parameter
    @return The reference to the service that was added (poObject). This allows fluid style usage of this routine                                                                      
------------------------------------------------------------------------------*/
METHOD PUBLIC Progress.Lang.Object AddService (poClass AS Progress.Lang.Class,
                                               poObject AS Progress.Lang.Object).

 

The method RemoveService removes a service instance from the Service Container (deregistration). The service to be removed from the service container is identified by the type of the service (instance of Progress.Lang.Class). The object instance is not explicitly deleted. 

RemoveService syntax
/*------------------------------------------------------------------------------
    Purpose: Removes the specified service type from the service container.                                                                       
    Notes:         
    @param poClass The reference to the class or interface of the service to remove from the service container.
------------------------------------------------------------------------------*/
METHOD PUBLIC VOID RemoveService (poClass AS Progress.Lang.Class).

 

 The method GetService requests a service instance from the service container. The service is identified by the type (instance of Progress.Lang.Class). When the requested service is not registered the unknown value is returned (null reference).

GetService syntax
/*------------------------------------------------------------------------------
    Purpose: Gets the service object of the specified type.                                                                       
    Notes:         
    @param poClass The reference to the class or interface of the service to return
    @return The reference to the instance of the service of ? when the service is not registered with the service container
------------------------------------------------------------------------------*/
METHOD PUBLIC Progress.Lang.Object GetService (poClass AS Progress.Lang.Class).

 

The default Service Container

The SmartComponent Library provides a single service container as the session default service container. This service container is accessible from the Consultingwerk.Framework.FrameworkSettings:ServiceContainer reference. This reference is of the IServiceContainer interface. Customers requiring the use of a different ServiceContainer (a custom implementation) may assign a different reference to the static property in the FrameworkSettings class.

When the application is started it is guaranteed that always a default Service Container instance is available from the static property.

Custom Service Containers

Additional Service Container instances may be useful in complex applications as well. Consider a complex Form where multiple controls may need to interact with each other. Rather than providing properties in each of the controls and assign each other’s references that way a Service Container managed by the Form may be a simpler way to resolve the dependencies. With a Service Container scoped to the Form it will also be possible to manage the dependencies in multiple parallel instances of the same Form. 

Service Interfaces

Service should be referenced through Interface types rather than through actual class names. This will allow application developers to leverage all of the advantages of dependency injection. In the SmartComponent Library we refer to the Interfaces used for requesting services from the Service Container as Service Interfaces (which is not to be misunderstood as the Service Interface in the OERA architecture).

Requesting Services from the Service Container

 

In order to request a service from a Service Container developers need to use the GetService method of the Service Container. The GetService method expects the service type (service interface) as the only input parameter and returns the service instance or unknown value when no service is registered.

GetService() sample using Progress.Lang.Class:GetClass()
DEFINE VARIABLE oSettingsService AS Consultingwerk.Framework.ISettingsService NO-UNDO .

oSettingsService = CAST (FrameworkSettings:ServiceContainer:GetService (Progress.Lang.Class:GetClass("Consultingwerk.Framework.ISettingsService")),
                         Consultingwerk.Framework.ISettingsService) . 

 

From OpenEdge 11.4 on, the GET-CLASS function might be used as an alternative to the Progress.Lang.Class:GetClass() method. The GET-CLASS function provides compile time verification of the type name as well as support for abbreviated class names through USING statements.

GetService() sample using GET-CLASS (OpenEdge 11.4)
USING Consultingwerk.Framework.* FROM PROPATH .
 
oSettingsService = CAST (FrameworkSettings:ServiceContainer:GetService (GET-CLASS (ISettingsService)),
                         ISettingsService) .

 

In both cases it is typically required to CAST the retrieved service reference to the target interface. This makes the code unfriendly to readers and the type name appears two times resulting in potential causes for errors: When the type names do not match.

For this reason we have introduced the get-service.i include file which uses the same type name parameter for the retrieval of the service from the default Service Container and to cast the return value:

 

Sample using get-service.i
DEFINE VARIABLE oSettingsService AS Consultingwerk.Framework.ISettingsService NO-UNDO .
       
oSettingsService = {Consultingwerk/get-service.i Consultingwerk.Framework.ISettingsService} .

 

The get-service.i Include file uses the Progress.Lang.Class:GetClass() method up to OpenEdge 11.3 and the GET-CLASS function from OpenEdge 11.4 on. For this reason it is required to pass the full type name (not relying on any USING statement) up to OpenEdge 11.3.

 

Adding Services to the Service Container

 

The SmartComponent Library and the WinKit provide various methods to add services to the Service Container.

Using the Service Loader

The most common way to add services to a service container is to provide an XML file (temp-table form) to an instance of the ServiceLoader class. The Service Loader then creates an instance of the classes provided in the XML file and registers them under the provided service type or service interface.

The Service Loader can load services to the default Service Container or to a custom Service Container instance. When loading service to a custom Service Container instance, then this Service Container instance needs to be passed as the optional parameter to the ServiceLoader constructor.

Sample loading Services from services.xml using ServiceLoader
USING Consultingwerk.Framework.* FROM PROPATH .

/* Load services using ServiceLoader */
DEFINE VARIABLE oLoader AS ServiceLoader NO-UNDO .
  
oLoader = NEW ServiceLoader () .
oLoader:Load ("Consultingwerk/SmartComponentsDemo/CustomerExplorer/services.xml":U) .  

 

The services.xml file has the following form:

 

Sample services.xml file
<?xml version="1.0"?>
<ttServiceLoader xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ttServiceLoaderRow>
    <Order>1</Order>
    <ServiceTypeName>Consultingwerk.OERA.Context.IContextDatasetFactory</ServiceTypeName>
    <ServiceClassName>Consultingwerk.OERA.Context.ContextDatasetFactory</ServiceClassName>
  </ttServiceLoaderRow>

  <ttServiceLoaderRow>
    <Order>2</Order>
    <ServiceTypeName>Consultingwerk.RollbaseAdapter.IRollbaseCredentials</ServiceTypeName>
    <ServiceClassName>Consultingwerk.RollbaseAdapter.RollbaseCredentials</ServiceClassName>
  </ttServiceLoaderRow>
   
</ttServiceLoader>

 

The ttServiceLoaderRow may have the following nested tags:

Tag Name

Description

Order

INTEGER The order in which services should be loaded, non-unique primary index

ServiceTypeName

CHARACTER Comma-delimited list of service interface types under which the service instance should be registered (1 or many)

ServiceClassName

CHARACTER The name of the class to create the instance from

Disabled

LOGICAL Optional, set to yes to disable processing of the current row

RequiredDatabases

CHARACTER Comma-delimited list of logical database names required to load the given entry. When the databases are not connected, the current row is ignored

Manually

In cases that services may require parameters to their constructor or are only needed under certain circumstances it is possible to register services manually using the ServiceContainer:AddService method:

Sample loading services manually
DEFINE VARIABLE oSmtpConfiguration AS SmtpConfiguration NO-UNDO .
  
oSmtpConfiguration = NEW SmtpConfiguration () .
oSmtpConfiguration:SmtpHostName   = oConfigurationProvider:GetValue ("SmtpHostName":U) .
oSmtpConfiguration:SmtpPassword   = oConfigurationProvider:GetValue ("SmtpPassword":U, "":U) .
oSmtpConfiguration:SmtpPortNumber = INTEGER (oConfigurationProvider:GetValue ("SmtpPortNumber":U, ?)) .
oSmtpConfiguration:SmtpSenderName = oConfigurationProvider:GetValue ("SmtpSenderName":U, "":U) .
oSmtpConfiguration:SmtpUserName   = oConfigurationProvider:GetValue ("SmtpUserName":U, "":U) .

FrameworkSettings:ServiceContainer:AddService (Progress.Lang.Class:GetClass ("Consultingwerk.Framework.ISmtpConfiguration":U),
                                               oSmtpConfiguration) .

At first usage through get-service.i

The get-service.i include file does support the initialization of a service instance at first usage through a second (optional) parameter to the include file:

Sample using get-service.i to load service instance at first usage
DEFINE VARIABLE oAdapter     AS IRollbaseAdapter     NO-UNDO .

oAdapter = {Consultingwerk/get-service.i Consultingwerk.RollbaseAdapter.IRollbaseAdapter
                                         "NEW Consultingwerk.RollbaseAdapter.RollbaseAdapter()"} .

 

This code sample returns an instance of the IRollbaseAdapter service. When no service of that type is registered yet, a new instance of the RollbaseAdapter class will be created (DYNAMIC-NEW) and registered in the Service Container as the IRollbaseAdapter service.

At first request through IServiceCreator callback

Planned feature, see SCL-573.

In a future enhancemet we will implement a callback to a factory class to support lazy loading of services or loading of services with specific constructor parameters.

Related documentation

 

 

 

  • No labels