Skip to content

Commit

Permalink
Working on documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
SuppieRK committed Feb 7, 2025
1 parent 8a488c7 commit 3c6f590
Show file tree
Hide file tree
Showing 19 changed files with 368 additions and 65 deletions.
48 changes: 46 additions & 2 deletions platform-library/src/main/java/platform/ApplicationWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,89 +5,133 @@
import io.javalin.Javalin;
import java.io.Closeable;
import platform.dependencies.ConfigurationProvider;
import platform.dependencies.GestaltProvider;
import platform.dependencies.ConfigurationReaderProvider;
import platform.dependencies.JacksonProvider;
import platform.dependencies.JavalinProvider;
import platform.dependencies.JooqProvider;
import platform.endpoints.HealthcheckEndpointGroup;
import platform.primitives.ServerPort;

/**
* Provides functionality similar to SpringBootApplication, wrapping application internals and
* preparing a majority of the functionality for use.
*/
public final class ApplicationWrapper implements Closeable {
private final Injector injector;
private final Memoized<Javalin> javalin;

/**
* Default constructor.
*
* @param injector with application dependencies.
*/
private ApplicationWrapper(Injector injector) {
this.injector = injector;
this.javalin = Memoized.memoizedSupplier(() -> injector.get(Javalin.class));
}

/**
* @return wrapper builder
*/
public static Builder createApplication() {
return new Builder();
}

/**
* @return alternative builder used to adjust existing dependencies - mainly for testing
*/
public Modifier modifyDependencies() {
return new Modifier(this);
}

/**
* @return Javalin web server to implement tests using {@link io.javalin.testtools.JavalinTest}
*/
public Javalin javalin() {
return javalin.get();
}

/** Starts Javalin web server. */
public void start() {
javalin().start(injector.get(ServerPort.class).get());
}

/** {@inheritDoc} */
@Override
public void close() {
injector.close();
}

/**
* Primary wrapper builder which registers any platform dependencies upfront and allows consumers
* to add their dependencies as well.
*/
public static class Builder {
private final Injector.Builder injectorBuilder;

/** Default constructor. */
private Builder() {
this.injectorBuilder =
Injector.injector()
// Registering default platform dependencies
.add(GestaltProvider.class)
.add(ConfigurationReaderProvider.class)
.add(ConfigurationProvider.class)
.add(JooqProvider.class)
.add(JacksonProvider.class)
.add(HealthcheckEndpointGroup.class);
}

/** Delegates to {@link Injector.Builder#add(Object, Object...)} */
public Builder add(Object object, Object... additionalObjects) {
injectorBuilder.add(object, additionalObjects);
return this;
}

/** Delegates to {@link Injector.Builder#add(Class, Class[])} */
public Builder add(Class<?> clazz, Class<?>... additionalClasses) {
injectorBuilder.add(clazz, additionalClasses);
return this;
}

/**
* @return {@link ApplicationWrapper} instance
*/
public ApplicationWrapper build() {
return new ApplicationWrapper(injectorBuilder.add(JavalinProvider.class).build());
}
}

/**
* Secondary wrapper builder, which copies all existing dependencies for the sake to provide
* customers with the ability to replace some dependencies for testing.
*/
public static class Modifier {
private final Injector.CopyBuilder builder;

/**
* Default constructor.
*
* @param wrapper to copy dependencies from
*/
private Modifier(ApplicationWrapper wrapper) {
this.builder = wrapper.injector.copy();
}

/** Delegates to {@link Injector.CopyBuilder#replace(Class, Class)} */
public <F, T extends F> Modifier replace(Class<F> from, Class<T> to) {
builder.replace(from, to);
return this;
}

/** Delegates to {@link Injector.CopyBuilder#replace(Object, Object)} */
public <F, T extends F> Modifier replace(F from, T to) {
builder.replace(from, to);
return this;
}

/**
* @return {@link ApplicationWrapper} instance
*/
public ApplicationWrapper build() {
return new ApplicationWrapper(builder.build());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package platform.contracts;

/**
* Defines common expectation for the functionality which reads properties from configuration.
*
* <p>The exact details about how the configuration is read are irrelevant.
*/
public interface ConfigurationReader {
/**
* Get a config for a path and a given class.
*
* @param path path to get the config for
* @param clazz class to get the class for
* @param <T> type of class to get
* @return the configuration as a specified type
*/
<T> T read(String path, Class<T> clazz);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Defines contracts to help decouple library-specific code from the rest of the components. */
package platform.contracts;
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,68 @@
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.flywaydb.core.Flyway;
import org.github.gestalt.config.Gestalt;
import platform.contracts.ConfigurationReader;
import platform.primitives.ApplicationName;
import platform.primitives.ServerPort;

/**
* Initializes common configuration used by other components.
*
* <p>This class is {@code final} which prohibits its replacement in the {@link
* io.github.suppierk.inject.Injector} exposed to the consumers.
*/
@Singleton
public final class ConfigurationProvider {
private final Gestalt gestalt;
private final ConfigurationReader reader;

/**
* Default constructor.
*
* @param reader to getch configuration with
*/
@Inject
public ConfigurationProvider(Gestalt gestalt) {
this.gestalt = gestalt;
public ConfigurationProvider(ConfigurationReader reader) {
this.reader = reader;
}

/**
* @return a {@link ServerPort} instance to start the app with
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public ServerPort serverPort() throws Exception {
return new ServerPort(gestalt.getConfig("port", Integer.class));
public ServerPort serverPort() {
return new ServerPort(reader.read("port", Integer.class));
}

/**
* @return an {@link ApplicationName} to set in the Swagger API
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public ApplicationName applicationName() throws Exception {
return new ApplicationName(gestalt.getConfig("name", String.class));
public ApplicationName applicationName() {
return new ApplicationName(reader.read("name", String.class));
}

/**
* Creates database connection and applies Flyway migrations.
*
* <p><b>NOTE</b>: this has to be {@link HikariDataSource} and not {@link javax.sql.DataSource},
* because {@link HikariDataSource} is {@link java.io.Closeable} - which will allow {@link
* io.github.suppierk.inject.Injector} to automatically release its resources.
*
* @return a {@link HikariDataSource} to query the database
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public HikariDataSource dataSource() throws Exception {
public HikariDataSource dataSource() {
final var config = new HikariConfig();
config.setDriverClassName(org.postgresql.Driver.class.getName());
config.setJdbcUrl(gestalt.getConfig("database.url", String.class));
config.setUsername(gestalt.getConfig("database.username", String.class));
config.setPassword(gestalt.getConfig("database.password", String.class));
config.setJdbcUrl(reader.read("database.url", String.class));
config.setUsername(reader.read("database.username", String.class));
config.setPassword(reader.read("database.password", String.class));
final var dataSource = new HikariDataSource(config);

// Apply database migration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package platform.dependencies;

import io.github.suppierk.inject.Provides;
import jakarta.inject.Singleton;
import org.github.gestalt.config.builder.GestaltBuilder;
import org.github.gestalt.config.exceptions.GestaltException;
import org.github.gestalt.config.source.ClassPathConfigSourceBuilder;
import platform.contracts.ConfigurationReader;

/**
* Factory to create an instance of {@link ConfigurationReader}.
*
* <p>Here we are using {@link org.github.gestalt.config.Gestalt} and this can be replaced as
* needed.
*
* <p>This class is <b>NOT</b> {@code final} to allow its replacement in the {@link
* io.github.suppierk.inject.Injector}, for example, during tests to be able to leverage
* TestContainers as can be seen in {@link platform.test.AbstractApplicationTest}.
*/
@Singleton
public class ConfigurationReaderProvider {
protected static final String CONFIGURATION_FILE = "application.yml";

/**
* Initialize new {@link ConfigurationReader} instance.
*
* @return {@link org.github.gestalt.config.Gestalt} as {@link ConfigurationReader}
* @throws Exception if {@link org.github.gestalt.config.Gestalt} cannot be instantiated
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public ConfigurationReader configurationReader() throws Exception {
final var gestalt =
new GestaltBuilder()
.addSource(
ClassPathConfigSourceBuilder.builder().setResource(CONFIGURATION_FILE).build())
.build();

gestalt.loadConfigs();

return new ConfigurationReader() {
@Override
public <T> T read(String path, Class<T> clazz) {
try {
return gestalt.getConfig(path, clazz);
} catch (GestaltException e) {
throw new IllegalStateException(e);
}
}
};
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,36 @@
import io.javalin.json.JavalinJackson;
import jakarta.inject.Singleton;

/**
* Provides singletons to manage {@link ObjectMapper} instances.
*
* <p>This class is {@code final} which prohibits its replacement in the {@link
* io.github.suppierk.inject.Injector} exposed to the consumers.
*/
@Singleton
public final class JacksonProvider {
private final ObjectMapper mapper;

public JacksonProvider() {
this.mapper = new ObjectMapper();
}

/**
* A good place to apply some common configuration, for example, date formatting.
*
* @return a {@link ObjectMapper} instance
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public ObjectMapper objectMapper() {
return mapper;
return new ObjectMapper();
}

/**
* @param objectMapper from {@link #objectMapper()} autowired by {@link
* io.github.suppierk.inject.Injector}
* @return a {@link JavalinJackson} instance to make sure Javalin and the rest of the codebase use
* the same {@link ObjectMapper}
*/
@Provides
@Singleton
@SuppressWarnings("unused")
public JavalinJackson javalinJackson() {
return new JavalinJackson(mapper, true);
public JavalinJackson javalinJackson(ObjectMapper objectMapper) {
return new JavalinJackson(objectMapper, true);
}
}
Loading

0 comments on commit 3c6f590

Please sign in to comment.