Fusion framework Examples

This page lists some common examples using Fusion framework.

Defining a Bean without injections

If a bean has an injection or an event listener it will be automatically defined as a bean, but in some cases, you want to define a bean on a plain class without any injection nor event listener. For such a case, you can mark the class with @Bean:

@Bean
public class MyService {
    public int magicNumber() {
        return 42;
    }
}

This simple definition enables you to inject this bean in other beans.

Defining a Bean with injection(s)

The common use case of a framework is to get injected some bean without exactly knowing how it was created or initialized. Here is how to get an injection with Fusion framework:

public class MyService {
    @Injection
    ConfigService config;

    // ...
}
IMPORTANT

do NOT use private fields, it is not yet supported.

Alternatively, you can use constructor injections:

@ApplicationScoped // any marker making it a bean, can be as simple as @Bean
public class MyService {
    private final ConfigService config; (1)

    public MyService(final ConfigService service) { (2)
        this.config = service;
    }

    protected MyService() { (3)
        this.config = null;
    }
}
  1. The injected values can be defined normally since injections happen with the constructor, you can even precompute the data you need in the bean and not store the injection itself,
  2. The constructor injection (the selected one is the most public one - public wins over protected ) and with the most parameters,
  3. The no-arg constructor - only needed for scopes using subclassing like @ApplicationScoped .

Create a bean in a custom fashion without a class

It can happen you need to reuse some custom factory and code the initialization of a bean. For such a case you can mark a method with @Bean. Injections can be done in the enclosing bean if needed:

@DefaultScoped // enclosing class is a bean
public class MyProducer {
    @Injection
    ConfigService conf;

    @Bean
    public DataSource dataSource() {
        return new DataSourcefactory(conf).create();
    }
}
IMPORTANT

as of today, you can mark the producer method with a scope but lazy scopes (like @ApplicationScoped are not really lazy until you implement yourself the lazyness - but scope is respected, i.e. if it is @ApplicationScoped it will be a singleton).

TIP

if the returned type implements AutoCloseable, close() will be called to destroy the bean instance(s).

Injection of a list/set

IMPORTANT

injections of List or Set are done by resolving the parameter type of the injection.

public class MyService {
    @Injection
    List<MySpi> implementations;
}
TIP

you can put on the implementations (or beans) the annotation @Order to sort their position in the List - ignored for Set.

Listen to an event

Beans can communicate loosely between them thanks to events. The bus is synchronous and sorted using @Order annotation.

public class MyListener {
    public void onStart(@OnEvent final Start start) {
        System.out.println("Application started");
    }

    public void onStop(@OnEvent final Stop stop) {
        System.out.println("Application stopped");
    }
}

Example of ordered event listener (default being 1000):

public void onStart(@OnEvent @Order(990) final Start start);
TIP

listening to Start event can enable a lazy instance (@ApplicationScoped) to be forced to be initialized.

TIP

an event can have more parameters, other parameters will be considered as injections (but the lookup will be destroyed after the method call if it is not @ApplicationScoped)

Emit an event

To emit an event simply inject the Emitter and send the needed event:

public class CustomerService {
    @Injection
    Emitter emitter;

    public void createCustomer(final Customer customer) {
        emitter.emit(customer);
    }
}

Create a configuration model

A configuration model is a record marked with @RootConfiguration:

@RootConfiguration("server")
public record ServerConfiguration(int port, String accessLogPattern) {}

This simple configuration will read the system properties server.port, server.accessLogPattern (or environment variables SERVER_PORT, SERVER_ACCESSLOGPATTERN) to fill the values. The instance of ServerConfiguration can be injected in any bean:

@Bean
public class MyServer {
    private final ServerConfiguration conf;

    public MyServer(final ServerConfiguration conf) {
        this.conf = conf;
    }

    // ...
}

If you want to customize the name of the property you can use @Property.

Finally, you can register you own source of values creating a bean of type ConfigurationSource.

IMPORTANT

List<OtherConfig> are supported, but you must set in the configuration <prefix for this list>.length to the length value of the list then the nested instances are configured using <prefix>.<index> starting at index 0. Ex: myconf.mylist.0.name=foo.

Create a JSON model

A JSON model is a record marked with @JsonModel:

@JsonModel
public record ServerConfiguration(int port, String accessLogPattern) {}

Then simply inject the JsonMapper in any bean to read/write such a model:

@Bean
public class MyServer {
    private final JsonMapper mapper;

    public MyServer(final JsonMapper mapper) {
        this.mapper = mapper;
    }

    // ... mapper.toString(serverconf) / mapper.fromString(ServerConfiguration.class, "{}");
}

Handle unknown JSON attributes

A JSON model is a record marked with @JsonModel:

@JsonModel
public record MyModel(
        // known attribute
        String name,
        // unknown attributes/extensions
        @JsonOthers Map<String, Object> extensions) {}

This will match this JSON:

{
  "name": "fusion",
  "x-foo": true,
  "boney": "M"
}

And convert it to the following record mapping: MyModel[name=fusion, extensions={x-foo=true,boney=M}].

Define a custom HTTP endpoint

Implement a custom explicit Endpoint bean

@Bean
public class MyEndpoint implements Endpoint {
    ....
}

Implement a custom implicit Endpoint

@HttpMatcher(...)
public CompletionStage<Response> myEndpoint(final Request request) {
    ....
}

// or


@HttpMatcher(...)
public Response myEndpoint(final Request request) {
    ....
}

Define a JSON-RPC endpoint

public class Endpoints {
    @JsonRpc("copy")
    public MyResult result(final MyInput input) {
        return new MyResult(input.name());
    }

    @JsonModel
    public record MyResult(String name) {
    }

    @JsonModel
    public record MyInput(String name) {
    }
}
TIP

you can use the configuration entry fusion.jsonrpc.binding to change the /jsonrpc default binding. You can also set fusion.jsonrpc.forceInputStreamUsage to true to force the input to be reactive instead of using default request Reader.

NOTE

you can review documentation page to see how to render OpenRPC as asciidoc or OpenAPI content.

Define a "reactive" JSON-RPC endpoint

public class Endpoints {
    private final MyRemoteService remote;

    public Endpoints(final MyRemoteService remote) {
        this.remote = remote;
    }

    @JsonRpc("remoteById")
    public CompletionStage<MyResult> result(final String id) {
        return remote.invoke(id);
    }
}

Register OpenRPC endpoint

It is possible to register an OpenRPC endpoint named openrpc to serve JSON-RPC specification:

@Bean
public OpenRPCEndpoint openrpc() {
    return new OpenRPCEndpoint()
        /*.setInfo(new Info(...))*/;
}

Start the container

To launch the application you need to start the container. It is done in two phases:

  • Configure the runtime

  • Launch the runtime.

Here is how to do it:

try (
    final var container = ConfiguringContainer
        .of() (1)
        .start() (2)
) {
    // use the container or just await for the end of the application
}
  1. Get a ConfiguringContainer which enables you to disable bean autodiscovery, to replace beans etc...
  2. Launch the runtime container (you can look up beans there).
TIP

you can also just reuse io.yupiik.fusion.framework.api.main.Launcher main which will start the default container. You can implement a custom Awaiter to not let the container shutdown immediately if you need - webserver does it by default. Finally you can also, using this launcher, inject Args to read the main arguments.

Test with JUnit 5

@FusionSupport (1)
class FusionSupportTest {
    @Test
    void run(@Fusion final Emitter emitter) { (2)
        assertNotNull(emitter);
    }
}
  1. Mark the class to run tests under a container context (it is started/stopped automatically),
  2. Inject container beans in test parameters (mark them with @Fusion ).

Alternatively you can run a single container for all tests:

@MonoFusionSupport
class FusionSupportTest {
    // same as before
}

Do a fat jar with maven shade plugin

Fatjar are not recommended in general but can be convenient for demo or CLI applications. Here is how to do it very easily with Apache Maven shade plugin:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.5.2</version>
  <dependencies>
    <dependency>
      <groupId>io.yupiik.maven</groupId>
      <artifactId>maven-shade-transformers</artifactId>
      <version>0.0.5</version>
    </dependency>
  </dependencies>
  <executions>
    <execution>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <shadedClassifierName>fat</shadedClassifierName>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <dependencyReducedPomLocation>${project.build.directory}/reduced-pom.xml</dependencyReducedPomLocation>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>io.yupiik.fusion.framework.api.main.Launcher</mainClass>
          </transformer>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionDocumentationTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionJsonSchemaTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionOpenRPCTransformer" />
        </transformers>
        <filters>
          <filter> <!-- optional but generally saner -->
            <artifact>*:*</artifact>
            <excludes>
              <exclude>module-info.class</exclude>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
              <exclude>META-INF/LICENSE.txt</exclude>
              <exclude>META-INF/LICENSE</exclude>
              <exclude>META-INF/NOTICE.txt</exclude>
              <exclude>META-INF/NOTICE</exclude>
              <exclude>META-INF/MANIFEST.MF</exclude>
              <exclude>META-INF/DEPENDENCIES</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
    </execution>
  </executions>
</plugin>