[Follow-up on ‘IOC1: It’s about reuse of functionality’]
Whenever someone shouts out “SOA” (or Service Oriented Architecture), most people associate it with stuff like webservices, transports (such as http or .net remoting), protocols (such as soap) and interoperability problems between different technologies (J2EE/.NET).
In essence I think SOA isn’t about webservices, though webservices enable the development of SOA. SOA is a way of structuring your application into services; webservices is a way of defining services. In general SOA frameworks are written with enterprise application development scenarios in mind and therefore commonly target webservices (and similar technologies, often next to inproc transports).
I think SOA and benefiting from it can perfectly be done without the complex stuff it is commonly associated with. I would like to present IoC-Containers as a facility to build Service Oriented Architectures within your everyday applications. IoC isn’t about transports, protocols, descriptive languages or interoperability. It’s about services!
Looking at the code sample from my previous post on IoC, typical usage of an IoC-Container.
public class DefaultLoginFunctionality: ILoginFunctionality
private ITinyTextFunctionality _resourceManager;
private ILogWriterFunctionality _errorLogger;
public DefaultLoginFunctionality()
{
_resourceManager = (ITinyTextFunctionality) Container.ServeInstance("TinyTextReader");
_errorLogger = (ILogWriterFunctionality) Container.ServeInstance("LogWriter");
}
public void DoLogin(string username, string password)
{
//
// login code
//
if (loginUnsuccessfull && _resourceManager != null && _errorLogger != null)
{
string logMessage = _resourceManager.GetString("LOGON_UNSUCCESSFUL_MSG");
_errorLogger.WriteLogEntry(string.Format(logMessage, username));
}
}
From the code above you might be able to recognize ‘services’ in the XxxFunctionality references.
For example:
The _errorLogger functionality is only known by its interface, a Type object that functions as the contract between consumer and service. The service does not share its implementation with the consumer.
Following another SOA guideline, the _errorLogger implementation should never share its private configuration/data/details with other implementations. Every functionalities implementation should be autonomous.
To give our code sample a bit more off a SOA feel, I refactored the code into the following:
public class DefaultLoginService: ILoginService
{
private ITinyTextService _resourceManager;
private ILogWriterService _errorLogger;
/** other services this service consumes **/
public DefaultLoginFunctionality()
{
_resourceManager = (ITinyTextService) Container.GetService(typeof(ITinyTextService));
_errorLogger = (ILogWriterService) Container. GetService (typeof(ILogWriterService));
}
public void DoLogin(string username, string password)
{
//
// login code
//
if (loginUnsuccessfull && _resourceManager != null && _errorLogger != null)
{
string logMessage = _resourceManager.GetString("LOGON_UNSUCCESSFUL_MSG");
_errorLogger.WriteLogEntry(string.Format(logMessage, username));
}
}
}
Please note:
1.) The type names changed from XxxFunctionality into XxxService.
2.) The container exposes the method .GetService, instead of .ServeImplementation
3.) The parameter for the .GetService call on the Container now uses the contract (an interface type) to query a service.
Below is the ITinyTextService implementation (from the _resourceManager reference) I use.
public class MyTinyTextServiceImplementation: ITinyTextService
{
private ILogWriterService _errorLogger;
private IPrivateConfigurationService _privateConfiguration;
public MyTinyTextServiceImplementation()
{
_errorLogger = (ILogWriterService) Container.ServeInstance(typeof(ILogWriterService));
_privateConfiguration = (IPrivateConfigurationService) Container.ServeInstance(typeof(IPrivateConfigurationService));
}
/** ITinyTextService implementation **/
}
Please note:
1.) The ITinyTextService uses an ILogWriterService, for its ‘log writing’ needs.
2.) In order to work autonomously the ITinyTextService uses a service called IPrivateConfiguration which allows a service to have its own configuration. The IPrivateConfiguration can be used to configure the ITinyTextService’s implementation. For example, specify its default culture.
Other aspects of SOAs:
1.) The ability to interoperate, transport over http, or ‘doing the webservices thing’.
IoC-Containers don’t facilitate here. If you would like to ‘do the webservices thing’ from within service out of an IoC-Container, you should use the service as a façade to your werservice. If the ILogWriterService wishes to log its messages over soap to another server somewhere over the internet, the ILogWriterService should do so internally.
Whenever you have more implementations of the ILogWriterService that all share the same contract (=implement the same interface), these can be replaced by one another without programming effort. In your development environment you might log to your system’s EventLog, even though in your production environment a webservice might be used.
2.) Versioning of contracts and services.
Versioning services in a Container is less complex than versioning webservices that aim for interopability. Once we decided on using .net types as contract we might as well use the versioning that comes with types in .net. Strongly typing the assemblies that contain the interface declarations does the trick. Once an interface definition is strongly typed and used by services, it should never change.
Adding new features to fix shortcomings in your existing interfaces should be done by defining new interfaces. Thanks to the 'multiple inheritance' of interfaces you are able to write services that comply with multiple contracts, you could fix shortcomings in your contract in a new service that complies with the old contract.
Interfaces should also be stored in assemblies separate from the interface’s implementations. This should be done to avoid dependencies to the technology used in the interface’s implementations.