Skip to end of metadata
Go to start of metadata

ano-prise MetaFactory

What is this about

MetaFactory is a dependency injection mechanism which is based on the assumption that each component has different implementation flavors. MetaFactory is meant for services, i.e. components which offers some methods via public interface. MetaFactory is based on linking and aliasing, consider following example.

Example

Lets say we have a CalculatorService that can add to numbers.

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorService.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorService.java').getText()
println '

' + data + '

'

We will have at least one real implementation:

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceImpl.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceImpl.java').getText()
println '

' + data + '

'

and a mock for testing:

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceMock.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceMock.java').getText()
println '

' + data + '

'

Usually, at least in our world, we would have more impls, but lets stick with two. So, if you are testing a construct like this in spring you will have to setup a real and a test context. Which also means that you'll have to add each new service to both contexts. Each time you add a new one.
Our approach is slightly different, we make the system know about all available implementations and switch the one used by using aliases. Another difference in our approach is that we configure factories, not implementation classes, allowing more complicated instantiation schemes. Here an example of a factory:

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceFactory.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceFactory.java').getText()
println '

' + data + '

'

So how does it work? Somewhere in your app you will have some initialization code, this code can be a context listener, a call from main method or whatever (for example reading a config file). This code will add the knowledge of available instances to the MetaFactory. Another piece of code will also setup the used aliases, therefor configuring the system, which of the implementations of a service should be used. This small test demonstrates it:

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTest.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTest.java').getText()
println '

' + data + '

'

Running this test will produce following output:

On mock called 2 + 2
On impl called 2 + 2

ensuring you that both testcases resolved and instantiated different instances.

Using classes not strings.

So now we moved the config from xml into java, fine, but we are still using strings which are not refactoring-proof or compile safe. What have we won? Nothing. Therefore we added a better Example:

SVN: ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTestWithExtensions.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTestWithExtensions.java').getText()
println '

' + data + '

'

Now you see, that we only use classes for service factory registration and resolver and no strings anymore. The built-in enum Extension contains many useful constants but you are not limited to them. The real advantage of this method is the compiler will prevent you from configuring an instance of FooService as CalculatorService which could happen if you configure it in an xml file.

Extensions

Extensions modify the name of the class. They say stuff like if you need an instance of a CalculatorService use the Remote one. Following Extensions are supported by built-in classes:

SVN: ano-prise/trunk/java/net/anotheria/anoprise/metafactory/Extension.java

groovy: Notify your administrator that "Bob Swift Atlassian Add-ons - Scripting" requires a valid license. Reason: EXPIRED

def data = new URL('http://svn.anotheria.net/opensource/ano-prise/trunk/java/net/anotheria/anoprise/metafactory/Extension.java').getText()
println '

' + data + '

'

  • No labels