Skip to content

Latest commit

 

History

History
153 lines (141 loc) · 9.94 KB

README.md

File metadata and controls

153 lines (141 loc) · 9.94 KB

Introduction

The provided CLI tool generates a single Maven module into which we can easily add a REST services and a client as well as write tests. However, typically we have, multi module Maven project. Hence, created sample modules to validate following:

  • Separate module for REST service and the REST client.
  • Calling another service using the REST client.

Multi module project

Layout

This was easy to achieve with following modules:

  • service: This module contains the REST service.
  • public: This module contains the REST client and anything that needs to be shared.

service module

This module contains the REST controller and uses the Micronaut Maven Plugin to build the fat jar. Created following two services:

  • user-service: Returns the user name given a user id.
  • hello-service: Calls the user-service to get the user name and then returns the greeting as "Hello userName".

public module

This module contains the declarative http client using the @Client annotation.

@Client(id = "userService", path = "/users")
public interface UserServiceClient {
    @Get("/{userId}")
    String getUser(long userId);
}

Defining http client configuration

As seen above, apart from the http Get method, the UserServiceClient has following two attributes for the @Client annotation:

  • path: This is the relative path.
  • id: This is the service ID to be used for looking up the configuration. The configuration at minimum contains the http url for the service as shown below:
micronaut:
  http:
    services:
      user-service:
        url: 'http://localhost:9090'

Note that the service ID changes from camel-case in @Client annotation to kebab-case in yaml file.

Making the http client available to the consumers

Consumers can use the http client by adding dependency on the public module. However, the configuration is not directly available to the consumer. Tried following options:

  1. Copied the above configuration yaml properties into consumers application.yml file which is not user-friendly.
  2. Used the additional configuration files option passing the consumer configuration file classpath as micronaut.config.files parameter value.
  3. Read the additional configuration file from another module using classpath and manually passed the properties as property source during application start up as mentioned here.

With all of the above options, the consumer has an overhead of making sure the configuration is defined properly. Ideally, consumer should just add dependency on the public module and import the client to consume it. This was achieved by defining a class implementing the ApplicationContextConfigurer interface and having the @ContextConfigurer annotation as below:

@ContextConfigurer
public class UserServiceClientConfigurer implements ApplicationContextConfigurer {
    @Override
    public void configure(final ApplicationContextBuilder builder) {
        final Map<String, Object> properties = readPropertiesYamlFileAsMap();
        builder.properties(properties);
    }
}

That's it. When consumer service starts then above class is auto-detected and its configure() method is called.

Micronaut AOT POC

Generated the above-mentioned http service client configurer using AOT as described in Micronaut AOT.

Code generator class

Created ServiceClientConfigurerSourceGenerator class implementing the io.micronaut.aot.core.AOTCodeGenerator interface.

  • The ID for the code generator used is service.client.configurer.source.generator
  • It expects comma-separated list of service client fully-qualified class names as service.client.classes property value.
  • It generates a class implementing the ApplicationContextConfigurer interface which loads the required properties.
  • Added org.expedientframework.common.ServiceClientConfigurerSourceGenerator as a service entry in io.micronaut.aot.core.AOTCodeGenerator. This step is not documented and without this the code generator will not be executed.
  • Added micronaut-aot-core Maven dependency. Without this dependency the build will fail with following error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project core: Fatal error compiling: java.lang.IllegalArgumentException: The argument does not represent an annotation type: AOTModule -> [Help 1]

Configuring the micronaut-maven-plugin

pom.xml

Added following plugin configuration to pom.xml

<plugin>
    <groupId>io.micronaut.build</groupId>
    <artifactId>micronaut-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>generate-service-client-sources</id>
            <goals>
                <goal>aot-analysis</goal>
            </goals>
            <phase>generate-sources</phase>
            <configuration>
                <enabled>true</enabled>
                <packageName>org.expedientframework.hello.aot</packageName>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.expedientframework.hello</groupId>
            <artifactId>public</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</plugin>
  • goal: Used the aot-analysis goal of the plugin.
  • phase: The default phase is package but used generate-sources since the generated class is needed for the tests to run successfully.
  • configuration:
    • enabled: Enabled AOT analysis by setting this to true. With this setting the AOT code generator will be executed as part of Maven build itself.
    • packageName: Package name is required and the generated class will be put into this package.
  • dependencies: Had to add dependency for the service client module since it is referenced in the generated source file.

aot.properties

Added aot.properties file at the root of the Maven module with following properties:

# Enable the code generator
service.client.configurer.source.generator.enabled = true
# Comma separated list of http service client class fully-qualified names
service.client.classes = org.expedientframework.hello.HelloServiceClient, org.expedientframework.user.UserServiceClient

Output

  • The generated files are under target/aot and are copied accordingly under target/classes.
  • If you want to view the generated Java files then they are under target/aot/jit/generated/sources.

With above changes and configurations, was able to generate and consume service client configurer classes during the build.

Observations

Maven dependencies

For the top most module, the parent is defined as micronaut-parent which has all the required dependencies declared with required versions.

<parent>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-parent</artifactId>
    <version>3.9.2</version>
</parent>

It was observed that using latest version for any of the dependencies does not work. For example, if latest version of logback is used then logging stops working.

AOT Code Generator

Tried to implement AOTCodeGenerator using following steps mentioned in Micronaut AOT.

  • Defined custom class org.expedientframework.user.MyResourceGenerator implementing the AOTCodeGenerator interface with ID as my.resource.generator.
  • Added aot.properties at the root of the Maven module which has property to enable the code generator as below:
my.resource.generator.enabled = true
greeter.message = Hello, world!

However, the code generator class was not getting invoked when maven plugin was run using following command:

mvn package -Dmicronaut.aot.enabled=true -Dmicronaut.aot.packageName=com.ajeydudhe

After looking into Micronaut sources and some trial & error, figured out that we need to add the code generator class as service entry under META-INF/services/io.micronaut.aot.core.AOTCodeGenerator.
For example, added file user-service/service/src/main/resources/META-INF/services/io.micronaut.aot.core.AOTCodeGenerator with below fully-qualified class name for the custom code generator:

org.expedientframework.user.MyResourceGenerator

With above, the custom code generator was getting invoked during compilation.