Dependency Injection + Eclipse Scout? [message #1412881] |
Wed, 27 August 2014 09:29 |
Jeremie Bresson Messages: 1252 Registered: October 2011 |
Senior Member |
|
|
I am curious to know if someone on this forum has experimented Dependency Injection (DI) with Eclipse Scout.
For those who need a pointer on those topics (Dependency Injection, Contextualized Dependency Lookup, Setter Dependency Injection, Constructor Dependency Injection, Field Injection, getter Injection) read this: Inversion of Control History
With the SERVICE construct in Eclipse Scout we do dependency lookup:
SERVICES.getService(MyService.class);
The service registry is based on top of OSGi (plugin mechanism offered by the eclipse stack).
As a consequence, when we want to write Unit Tests with fake services (mock), we need to have the Scout Framework Layer up and running. This induces two consequences for the tests:
- They need to use ScoutClientTestRunner or ScoutServerTestRunner as runner (JUnit @RunWith)
- The execution of those tests is not as simple as it could be ("Run As -> JUnit Test" is not possible. You need a Test Application, a Maven+Tycho Build or "Run As -> JUnit Plug-in Test" with another configuration the unusable default).
Recently I have seen a team using this construct to test their server-services as plain JUnit Test.
If MyService uses AService and ISqlService the references are stored in private member. The service provides 2 constructors: one without any argument and one that sets the members.
public class MyService extends AbstractService implements IYService {
final AService aService;
final ISqlService sqlService;
public MyService() {
this(SERVICES.getService(AService.class), SERVICES.getService(ISqlService.class));
}
public MyService(AService aService, ISqlService sqlService) {
super();
this.aService = aService;
this.sqlService = sqlService;
}
//implementation
}
These allow writing plain JUnit tests where the second constructor is used to pass mock services instead of the real implementation...
A sort of manual Dependency Injection...
I am not sure of the impact of server boot time (because when MyService is instantiated, it will do the lookup of all services it uses). The whole dynamic aspect of services is also lost (but who is using this feature anyway? The only use case I know is also for test cases).
If you do so, be careful: services in scout are instantiated only ones (singletons). This works with services but not with other Scout construct like ServerSession (ServerSession.get() will provide the object corresponding to the user executing the request).
In any case, I am interested in your opinion on this topic.
.
|
|
|
|
Re: Dependency Injection + Eclipse Scout? [message #1413283 is a reply to message #1412881] |
Thu, 28 August 2014 07:56 |
|
It is correct that, OSGI Services shouldn't be stored localy. This is a Workaround and it has some limitations. It works, but it is also dangerous.
A better solution could be if SERVICE can be feed with mock services in JUnit setup and then run the code as normal JUnit Test.
@Before
public void setUp() {
SERVICE.add(new MyMockService());
}
and then in the productive code the usual way to access services can take place.
SERVICE.getService(IMyService.class);
Another solution can be a ServiceProvider which is injectable. Then it is possible to use a custom Test Service Provider in JUnit Context or the real ServiceProvider for production.
|
|
|
|
Re: Dependency Injection + Eclipse Scout? [message #1414084 is a reply to message #1413375] |
Sat, 30 August 2014 07:24 |
Jeremie Bresson Messages: 1252 Registered: October 2011 |
Senior Member |
|
|
We discussed with Erich how the correct pattern would looks like, here an improved version:
You need a provider class that is responsible for doing the service call (SERVICES.getService(..)) when it is invoked. The interface could looks like this:
import org.eclipse.scout.service.IService;
public interface IScoutServiceProvider<S extends IService> {
/**
* @return the service instance
*/
S getService();
}
(Side note: if you use Java 8, you recognize a SAM type (Single Abstract Method interface), which means that this class could be a lambda expression... But I develop this example with the Java6 constructs).
A possible implementation could be:
import org.eclipse.scout.service.IService;
import org.eclipse.scout.service.SERVICES;
public class ScoutServiceProvider<T extends IService> implements IScoutServiceProvider<T> {
private final Class<T> m_serviceInterfaceClass;
private ScoutServiceProvider(Class<T> serviceInterfaceClass) {
m_serviceInterfaceClass = serviceInterfaceClass;
}
@Override
public T getService() {
return SERVICES.getService(m_serviceInterfaceClass);
}
public static <S extends IService> IScoutServiceProvider<S> newProvider(Class<S> serviceInterfaceClass) {
return new ScoutServiceProvider<S>(serviceInterfaceClass);
}
}
This way MyService looks like this:
MyService will use the provider and store them instead of the services references. Notice the empty constructor of the service. In the service, where you were using "SERVICE.getService(AService.class)" the provider should be used "m_AServiceProvider.getService()"
public class MyService extends AbstractService implements IMyService {
final IScoutServiceProvider<AService> m_AServiceProvider;
final IScoutServiceProvider<ISqlService> m_SqlServiceProvider;
public MyService() {
this(ScoutServiceProvider.newProvider(AService.class), ScoutServiceProvider.newProvider(ISqlService.class));
}
public MyService(IScoutServiceProvider<AService> aServiceProvider, IScoutServiceProvider<ISqlService> sqlServiceProvider) {
super();
this.m_AServiceProvider = aServiceProvider;
this.m_SqlServiceProvider = sqlServiceProvider;
}
//...
@Override
public void doSomething() throws ProcessingException {
AFormData formData = new AFormData;
formData = m_AServiceProvider.getService().load(formData);
//...
}
}
For your tests, you can have a small utility method, creating a simple provider, that do not call SERVICE.getService(..) but return what will be your mocked implementation:
public static <T extends IService> IScoutServiceProvider<T> createProvider(final T serviceImplementation) {
return new IScoutServiceProvider<T>() {
@Override
public T getService() {
return serviceImplementation;
}
};
}
Your JUnit tests will looks like this:
AService aServiceMock;
ISqlService sqlServiceMock;
//Initialize the mock (with Mockito for example, or using plain Java if you prefer).
MyService serviceUnderTest = new MyService(createProvider(aServiceMock), createProvider(sqlServiceMock));
// execute tests on your service instance.
Of course, if you do not like the anonymous class approach, you can use a real class doing the same job. (or a lambda if you are on Java 8)
This pattern is absolutely correct. I think it will have only a small impact on the performance (I think the statement is inlined by the Just in Time compiler (JIT) of the JVM). You just have a lot of boilerplate code, making your Services a little bit verbose.
I think the pattern we have defined is exactly what a Dependency Injection Framework would do (sort of manual DI). A DI framework is doing the exact same thing, without writing the lines I have described here (at runtime (like with Guice) or at compile time (like with Dagger 2)).
@Urs: Thank you for your feedback. It is good to know how many projects based on Scout are interested by DI. If you want to use this pattern in your application, this second pattern is the correct one.
.
[Updated on: Sat, 30 August 2014 07:34] Report message to a moderator
|
|
|
Re: Dependency Injection + Eclipse Scout? [message #1415124 is a reply to message #1414084] |
Tue, 02 September 2014 06:33 |
|
In our project we use Scout together with Google Guice. We've made some small changes to the following Scout classes which enables DI support for Scout services and sessions:
* ClientProxyServiceFactory
* ClientServiceFactory
* ClientSessionRegistryService
For instance, the Scout's default class ClientServiceFactory creates a new instance of a Service class in this way:
private void updateInstanceCache(ServiceRegistration registration) {
...
m_service = m_serviceClass.newInstance();
if (m_service instanceof IService) {
((IService) m_service).initializeService(registration);
}
...
}
In order to use DI with Google Guice we must create an instance of a class with the Guice injector. That's why our DIClientServiceFactory does this instead:
private void updateInstanceCache(ServiceRegistration registration) {
...
m_service = ClientDI.getInstance(m_serviceClass); // required to perform DI on new instance
if (m_service instanceof IService) {
((IService) m_service).initializeService(registration);
}
...
}
ClientDI looks like this. Basically all it does is to provide access to the Guice injector and the module which contains the DI configuration.
final class ClientDI {
private ClientDI() {
}
private static Injector injector = Guice.createInjector(new ClientDIModule());
static <T> T getInstance(Class<T> clazz) {
return injector.getInstance(clazz);
}
}
In our plugin.xml we must reference the DIClientServiceFactory with a higher ranking than Scout's default service factory:
<service
createImmediately="false"
class="com.foo.client.util.DIClientServiceFactory"
ranking="1000">
</service>
Now all your services are created with DI and all dependencies are injected by the DI framework, according to your module configuration. An example from our Guice module, to provide a Scout service looks like this:
@Provides
public IDeviceService getDeviceService() {
return SERVICES.getService(IDeviceService.class);
}
In our productive code, a class which has a dependency to the IDeviceService looks like this:
public class CardTerminal implements ICardTerminal {
@Inject
public CardTerminal(IDeviceService deviceService, IFooConfig fooConfig) {
this.deviceService = deviceService; // a Scout service
this.fooConfig = fooConfig; // a non-Scout class instance managed by the DI framework
}
...
}
In our code we never call SERVICES.getService() directly. Instead, all services are injected as you've seen in the example above. When you're working with unit-tests, you don't need the DI framework at all. You simply pass a mock-object that implements IDeviceService to the CardTerminal CTOR.
We've also created a DIFormFactory, which creates instances of Scout forms with DI. Important: when you do this, you should no longer create form instances with Java's "new" operator (new MyForm()), instead you must always create forms by using the DI form factory, otherwise you'd simply bypass the DI injector.
A nice side-effect of this approach is that large parts of our code can be tested with plain Java unit-tests, only some tests require plug-in based test with a running OSGI / Scout framework.
Eclipse Scout Homepage | Documentation | GitHub
|
|
|
Powered by
FUDForum. Page generated in 0.03051 seconds