Nuxeo Open Kitchen
Published in

Nuxeo Open Kitchen

Building A Nuxeo Component (an opinionated way)

Picture by Randy Fath on Unsplash

The Nuxeo Platform was designed from the beginning to be a Service Oriented Platform. We embraced SOA from the beginning and that’s one of the reason why it’s so easy to do calls like Framework.getService(MyService.class) from every part of the code in Nuxeo.

We definitely love doing pluggable services, and when we want to introduce a new feature, our DNA tells us: what extension point and what service can we provide? Throughout the years, even the runtime part of the platform has evolved and it’s now even easier to implement a new component.

In this article, I will expose how I now build Nuxeo components and what pattern I like to use and why. This is my own way to do it in September 2019. It is always evolving, so perhaps in two years I will call it crap. You’ve been warned!

Someone Needs Access Tokens

To illustrate how we can create a component, we will take the example of a service providing OAuth2 Tokens for the client credentials flow. It’s quite simple and just requires the IDP url, a clientId, a clientSecret and a scope. We want to be able to call our service like this in the platform:

AccessTokenProvider provider = Framework.getService(AccessTokenProvider.class);
byte[] accessToken = provider.getToken(myIPD);

Let’s first create our Nuxeo project using the Nuxeo CLI.

# mkdir nuxeo-oauth-client-credentials
# nuxeo bootstrap single-module
...
create Generating Single module
info Parameters: Parent group, Parent artifact, Parent version, Nuxeo version, Project group, Project artifact, Project version, Project description
? Parent Group id (use white space to cancel default value.): org.nuxeo.ecm.distribution
? Parent Artifact id: nuxeo-distribution
? Parent Version: 10.10
? Project Group id: org.nuxeo.oauth2
? Project Artifact id: nuxeo-oauth-client-credentials
? Project version: 1.0-SNAPSHOT
? Project description:
create pom.xml
create src/main/resources/META-INF/MANIFEST.MF
create src/main/java/org/nuxeo/oauth2/package-info.java
create src/test/resources/jndi.properties
create src/test/resources/log4j.xml
info You can start editing code or you can continue with calling another generator (nuxeo bootstrap [<generator>..])

and our first test:

# nuxeo bootstrap test-empty
...
? Unit-Test package: org.nuxeo.oauth2
? Unit-Test class name: AccessTokenProviderTest
? Using Feature: None
info Maven dependency: org.nuxeo.runtime:nuxeo-runtime-test:::test
info Maven dependency: org.nuxeo.ecm.platform:nuxeo-platform-test:::test
info Maven dependency: org.nuxeo.ecm.platform:nuxeo-platform-audit-core::test-jar:test
info Maven dependency: org.nuxeo.ecm.automation:nuxeo-automation-test:::test
force pom.xml
create src/test/java/org/nuxeo/oauth2/AccessTokenProviderTest.java
info You can start editing code or you can continue with calling another generator (nuxeo bootstrap [<generator>..])

The Nuxeo CLI bootstraps a test for us and then is already using the Nuxeo Test Framework. We will just remove the line where it injects the CoreSession because we don't need it (hence the None answered to the Using Feature question).

For the moment the content of thet test will be super simple, and will just verify the assertion that we exposed:

@RunWith(FeaturesRunner.class)
@Features(RuntimeFeature.class)
@Deploy("org.nuxeo.oauth2.nuxeo-oauth-client-credentials")
public class AccessTokenProviderTest {

@Test
public void can_generate_access_token() {
AccessTokenProvider provider = Framework.getService(AccessTokenProvider.class);

assertNotNull(provider);
byte[] accessToken = provider.getToken("myIdp");
assertTrue(accessToken.length > 0);
}
}

Now that we have a test, we can start working on making it pass.

For the moment, our test doesn’t even compile! To fix that, we can create the corresponding interface and veryfiy that our test is failing. That’s a first step: low code is trendy, but low code is not no code and Nuxeo is not magic, we’ll have to code more!

This will be the start of our bundle. The test express exactly what we want from a consumer standpoint. Now, for the Framework.getService() call to work, Nuxeo asks us to define a component whose responsibility will be to provide this service. A component in Nuxeo is at first nothing but a simple XML file that defines the component name, its implementation (optional) and its capabilities:

<component name="org.nuxeo.oauth2.OAuth2Component"
version="1.0">

<implementation
class="org.nuxeo.oauth2.OAuth2Component" />. (1)

<service>
<provide interface="org.nuxeo.oauth2.AccessTokenProvider" /> (2)
</service>

</component>
  1. The implementation class of our component. It is optional as our component may just be a generic component that contribute to others. That’s the case of all single contribution in Nuxeo.
  2. The service that this component provides.

If we want that component to be deployed, we have to append it to the list of component that are deployed in our bundle. We can do that by editing the MANIFEST.MF file of our bundle, located in src/main/resources/META-INF:

Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Manifest-Version: 1.0
Bundle-Vendor: org.nuxeo.oauth2
Bundle-Version: 1.0.0
Bundle-Name: nuxeo-oauth-client-credentials
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.nuxeo.oauth2.nuxeo-oauth-client-credentials;singleton=true
Nuxeo-Component: OSGI-INF/oauth-token-component.xml

If we try to execute our test again, it will fail with an error that tells that our component cannot be cast to AccessTokenProvider. One way to quickly fix this is to actually make our component implement the interface. The component could then implement the needed method and we would be done.

However the SRP principle tells us to ask ourselves what is the responsibility of the component: to provide a service, not to implement it! That’s a big difference and that’s why we can override the getAdapter() method to provide an alternate implementation than return this;. Our component becomes this:

public class OAuth2Component extends DefaultComponent {

private AccessTokenProvider instance;

@Override
public <T> T getAdapter(Class<T> adapter) {
if (AccessTokenProvider.class.equals(adapter)) {
return (T) getAccessTokenProvider();
} else {
return super.getAdapter(adapter);
}
}

private AccessTokenProvider getAccessTokenProvider() {
if (instance == null) {
instance = new ClientCredentialsAccessTokenProvider();
}
return instance;
}

}

with a very naive and lazy implementation of ClientCredentialsAccessTokenProvider, our test can become green. We've implemented our first component! Now we have to make it pluggable so that our token may be configurable.

The OAuth2 Component Becomes Configurable

As we said earlier, our AccessTokenProvider has to be configurable, with an Url, some client credentials and a scope. The configuration for that component could look like a contribution like this:

<component name="org.nuxeo.oauth2.contrib-test" version="1.0">

<extension target="org.nuxeo.oauth2.OAuth2Component"
point="idp">
<oauth2 id="myIdp">
<tokenUrl>https://my-idp.com/oauth2/token</tokenUrl>
<clientId>clientId</clientId>
<clientSecret>myClientSecret</clientSecret>
<scope>test</scope>
</oauth2>
</extension>
</component>

We will add this contribution in the src/test/resources folder of our project and update the test to deploy that contribution component to our test by adding @Deploy("org.nuxeo.oauth2.nuxeo-oauth-client-credentials:oauth2-contrib.xml") to our test deployment list. We also have to write a test to verify the configuration. As our current implementation returns a dumb token for every request, we will write a test that ensure that I get an Exception when I point to a non existent IDP configuration:

@Test(expected = OAuth2Exception.class)
public void can_not_have_token_for_unexisting_idp_configuration() throws Exception {
atp.getToken("otherIdp");

}

Nothing will change from the previous outcome except that now we have a message in the logs yelling at us that we tried to deploy a component on a missing extension point:

====================================================================
= Component Loading Status: Pending: 0 / Missing: 1 / Unstarted: 0 / Total: 12
* service:org.nuxeo.oauth2.contrib-test references missing [target=org.nuxeo.oauth2.OAuth2Component;point=idp]
====================================================================

It means that our OAuth2Component doesn't have the idp extension point. To fix this, we have to update the component definition and tell the Nuxeo runtime how to interpret the configuration. We will add a small snippet of XML to the component definition:

<extension-point name="idp">
<object class="org.nuxeo.oauth2.IdpDescriptor" />
</extension-point>

It tells the framework that the component accepts some IdpDescriptor on its idp extension point. The IdpDescriptor has still to be created to fulfill the definition. The XMap library allows to to map an XML document onto a Java Object.

Since Nuxeo 10.3, implementing the Descriptor interface for descriptor allow them to be automatically register them into the DefaultComponent registry and refer to it, without having to implements the usual registerContribution method. The list of descriptor registered for one component and an extension point name can be fetched using the DefaultComponent#getDescriptors() method.

Given the fact that descriptors are automatically registered, the component’s code becomes:

private AccessTokenProvider instance;

@Override
public <T> T getAdapter(Class<T> adapter) {
if (AccessTokenProvider.class.equals(adapter)) {
return (T) getAccessTokenProvider();
} else {
return super.getAdapter(adapter);
}
}

private AccessTokenProvider getAccessTokenProvider() {
if (instance == null) {
instance = new ClientCredentialsAccessTokenProvider(getDescriptors("idp"));
}
return instance;
}

This allows to change the implementation of our service by using the list of descriptors:

private Map<String, IdpDescriptor> descriptors;

public ClientCredentialsAccessTokenProvider(List<IdpDescriptor> descriptors) {
this.descriptors = descriptors.stream().collect(Collectors.toMap(IdpDescriptor::getId, Function.identity()));
}

@Override
public byte[] getToken(String idpId) throws OAuth2Exception {
if (descriptors.containsKey(idpId)) {
return "token".getBytes();
} else {
throw new OAuth2Exception();
}
}

With this implementation, our test then turns green. The AccessTokenProvider has now a dependency on the list of IdpDescriptor that is managed by the OAuth2Component. For the sake of the experience we will launch an Event with the EventService in order to add a dependency on another service. This is indeed a very common pattern in Nuxeo to call a service from another service, and will help us in the next section to demonstrate some refactoring.

@Override
public byte[] getToken(String idpId) throws OAuth2Exception {
if (descriptors.containsKey(idpId)) {
Framework.getService(EventService.class).sendEvent(new Event("accessTokenTopic", idpId, this, null));
return "token".getBytes();
} else {
throw new OAuth2Exception();
}
}

Let’s Refactor

Extract the dependencies

The first problem that we can see is that the ClientCredentialsAccessTokenProvider doesn't clearly expres the dependencies to the other services. The reason is that various calls to Framework.getService(EventService.class) make the dependencies difficult to identify and also more difficult to test. We will move the EventService as a field of our object and intialize it with the help of the Builder pattern: it will allow to initalize our service more easily in a test. The initialization to the default value can be conditioned to a call at Framework.isInitialized():

public static class Builder {
ClientCredentialsAccessTokenProvider provider;

public Builder() {
provider = new ClientCredentialsAccessTokenProvider();
if (Framework.isInitialized()) {
provider.eventService = Framework.getService(EventService.class);
}
}

public Builder descriptors(List<IdpDescriptor> descriptors) {
provider.descriptors = descriptors.stream()
.collect(Collectors.toMap(IdpDescriptor::getId, Function.identity()));
return this;
}

public Builder eventService(EventService eventService) {
provider.eventService = eventService;
return this;
}

ClientCredentialsAccessTokenProvider build() {
return provider;
}
}

The ClientCredentialsAccessTokenProvider is then not responsible for its initilization which allows to build out a complete object in a unit test and mocking the dependencies that we are not interested in. Our object becomes simpler and doesn't call Framework.getService() directly anymore:

public class ClientCredentialsAccessTokenProvider implements AccessTokenProvider {

// Service dependencies
private Map<String, IdpDescriptor> descriptors;

private EventService eventService;

@Override
public byte[] getToken(String idpId) throws OAuth2Exception {
if (descriptors.containsKey(idpId)) {
eventService.sendEvent(new Event("accessTokenTopic", idpId, this, null));
return "token".getBytes();
} else {
throw new OAuth2Exception();
}
}
...

The difference may be very subtle, but it makes a whole difference. Now we are able to inject a mock in place of the eventService and verify the interaction of our service with its dependency. When the dependency is hard and/or long to setup in an integration test, you usually do not test it thoroughly enough. Here is how we can do:

If everything had been packed in one single class (the component/service all in one object), it would have been a bit harder to test and less obvious because of the difficulties to identify dependencies.

Simplify Our Component

Looking back at our component object, there is a pattern that we often see in Nuxeo: a lazy instanciation of an object:

if(instance == null) {
instance = buildMyInstance();
}
return instance;

That pattern is used here only once, but as soon as our component will have several services to provide, we may have more that one and we will have to repeat that pattern. Instead of repeating the pattern by copy pasting, we can extract it with the flightweight pattern. To explain it, let’s look at the test we can write for it:

LazyProvider<Dummy> lp = new LazyProvider<>(callable);          (1)

// When I call it twice
verify(callable, never()).call();
Dummy dummy1 = lp.get();
Dummy dummy2 = lp.get();

// Then the underlying construction happened only once
verify(callable, times(1)).call(); (2)

It’s basically a wrapper around a callable that keeps the instance in its internal state:

  1. The LazyProvider is supposed to provide a Dummy interface using a callable to build it.
  2. We can verify that calling twice our LazyProvider only instanciate once our Dummy Object.

Let’s apply this to our component and look at the difference:

public class OAuth2Component extends DefaultComponent {

private LazyProvider<AccessTokenProvider> atp = new LazyProvider<>(this::buildAccessTokenProvider);

@Override
public <T> T getAdapter(Class<T> adapter) {
if (AccessTokenProvider.class.equals(adapter)) {
return (T) atp.get();
}
return super.getAdapter(adapter);
}

private AccessTokenProvider buildAccessTokenProvider() {
return builder().descriptors(getDescriptors("idp")).build();
}
}

Our component is now only 17 lines long! It still supports the registration of extension points and accepts contribution to build our AccessTokenProvider service. I like defining the responsibilities of a given object: here the responsiblity of our component is only to know how to instanciate the implementation of the service it provides. Everything else was extracted :

  • to the Nuxeo Runtime by using the default registry mechanism (new feature of Nuxeo LTS 2019)
  • to some utility classes
  • to the real implementation of the service

Conclusion

Thru this how-to we’ve seen how to create a Nuxeo component and also that new Nuxeo LTS 2019 allows to create more concise Nuxe Runtime Components. Having to write less code allows you to write less bugs too!

The pattern of extracting the implementation of the interface from the component allows to test it in a more efficient manner. In our example, we relied only on one service, but if our component talks to dozen of different Nuxeo services, setting them up takes time and effort. By mocking those service, we can test the interactions more easily without having to test the implementation.

The code of the example is available on Github. It features a real implementation of the OAuth2 client credentials flow that allows to see how to thoroughly test its implementation with an external web service.

References

Originally published at https://dmetzler.github.io on September 16, 2019.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store