Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DI with Guice #23

Merged
merged 1 commit into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ dependencies {
implementation project(':scale')
implementation project(':transport')

implementation 'com.google.inject:guice:5.1.0'

annotationProcessor project(':pallet:pallet-codegen')
annotationProcessor project(':rpc:rpc-codegen')

testImplementation project(':tests')

testAnnotationProcessor project(':pallet:pallet-codegen')

testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'org.testcontainers:testcontainers:1.17.1'
testImplementation 'org.testcontainers:junit-jupiter:1.17.1'
testImplementation 'org.testcontainers:testcontainers:1.17.2'
testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
testImplementation 'org.awaitility:awaitility:4.2.0'
}

testAnnotationProcessor project(':pallet:pallet-codegen')
}
45 changes: 21 additions & 24 deletions api/src/main/java/com/strategyobject/substrateclient/api/Api.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
package com.strategyobject.substrateclient.api;

import com.strategyobject.substrateclient.pallet.GeneratedPalletResolver;
import com.strategyobject.substrateclient.pallet.PalletResolver;
import com.strategyobject.substrateclient.rpc.RpcGeneratedSectionFactory;
import com.strategyobject.substrateclient.rpc.api.section.State;
import com.google.inject.Module;
import com.strategyobject.substrateclient.pallet.PalletFactory;
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
import com.strategyobject.substrateclient.transport.ProviderInterface;
import lombok.NonNull;
import lombok.val;
import lombok.RequiredArgsConstructor;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
* Provides the ability to query a node and interact with the Polkadot or Substrate chains.
* It allows interacting with blockchain in various ways: using RPC's queries directly or
* accessing Pallets and its APIs, such as storages, transactions, etc.
*/
@RequiredArgsConstructor
public class Api implements AutoCloseable {
private final @NonNull ProviderInterface providerInterface;
private final @NonNull PalletResolver palletResolver;
private final @NonNull RpcSectionFactory rpcSectionFactory;
private final @NonNull PalletFactory palletFactory;
private final Map<Class<?>, Object> resolvedCache = new ConcurrentHashMap<>();

private Api(@NonNull ProviderInterface providerInterface) {
this.providerInterface = providerInterface;

val state = RpcGeneratedSectionFactory.create(State.class, providerInterface);
this.palletResolver = GeneratedPalletResolver.with(state);
}

/**
* Resolves the instance of a rpc by its definition.
Expand All @@ -35,9 +30,8 @@ private Api(@NonNull ProviderInterface providerInterface) {
* @param <T> the type of the rpc
* @return appropriate instance of the rpc
*/
public <T> T rpc(Class<T> clazz) {
return clazz.cast(resolvedCache
.computeIfAbsent(clazz, x -> RpcGeneratedSectionFactory.create(x, providerInterface)));
public <T> T rpc(@NonNull Class<T> clazz) {
return clazz.cast(resolvedCache.computeIfAbsent(clazz, rpcSectionFactory::create));
}

/**
Expand All @@ -48,18 +42,21 @@ public <T> T rpc(Class<T> clazz) {
* @return appropriate instance of the pallet
*/
public <T> T pallet(@NonNull Class<T> clazz) {
return clazz.cast(resolvedCache
.computeIfAbsent(clazz, palletResolver::resolve));
}

public static Api with(ProviderInterface providerInterface) {
return new Api(providerInterface);
return clazz.cast(resolvedCache.computeIfAbsent(clazz, palletFactory::create));
}

@Override
public void close() throws Exception {
if (providerInterface instanceof AutoCloseable) {
((AutoCloseable) providerInterface).close();
if (rpcSectionFactory instanceof AutoCloseable) {
((AutoCloseable) rpcSectionFactory).close();
}
}

public static ApiBuilder<DefaultModule> with(@NonNull Supplier<ProviderInterface> providerInterface) {
return with(new DefaultModule(providerInterface.get()));
}

public static <M extends Module> ApiBuilder<M> with(@NonNull M module) {
return new ApiBuilder<>(module);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.strategyobject.substrateclient.api;

import com.google.inject.Guice;
import com.google.inject.Module;
import com.strategyobject.substrateclient.transport.ProviderInterface;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;

@RequiredArgsConstructor
public class ApiBuilder<M extends Module> {
private final @NonNull M module;

public ApiBuilder<M> configure(@NonNull Consumer<M> configuration) {
configuration.accept(module);
return this;
}

public <N extends Module> ApiBuilder<N> reconfigure(@NonNull Function<M, N> configuration) {
return new ApiBuilder<>(configuration.apply(this.module));
}

public CompletableFuture<Api> build() {
val injector = Guice.createInjector(new RequireModule(), module);
val provider = injector.getInstance(ProviderInterface.class);
val result = provider.isConnected() ?
CompletableFuture.<Void>completedFuture(null) :
provider.connect();

return result.thenApply(ignored -> injector.getInstance(Api.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.strategyobject.substrateclient.api;

import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.strategyobject.substrateclient.common.types.AutoRegistry;
import com.strategyobject.substrateclient.pallet.GeneratedPalletFactory;
import com.strategyobject.substrateclient.pallet.PalletFactory;
import com.strategyobject.substrateclient.rpc.GeneratedRpcSectionFactory;
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
import com.strategyobject.substrateclient.rpc.api.section.State;
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
import com.strategyobject.substrateclient.transport.ProviderInterface;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

import java.util.function.Consumer;

@RequiredArgsConstructor
public class DefaultModule extends AbstractModule {
private final @NonNull ProviderInterface providerInterface;

private Consumer<ScaleReaderRegistry> configureScaleReaderRegistry = this::autoRegister;
private Consumer<ScaleWriterRegistry> configureScaleWriterRegistry = this::autoRegister;
private Consumer<RpcDecoderRegistry> configureRpcDecoderRegistry = this::autoRegister;
private Consumer<RpcEncoderRegistry> configureRpcEncoderRegistry = this::autoRegister;

private void autoRegister(AutoRegistry registry) {
registry.registerAnnotatedFrom("com.strategyobject.substrateclient");
}

public DefaultModule configureScaleReaderRegistry(Consumer<ScaleReaderRegistry> configure) {
configureScaleReaderRegistry = configureScaleReaderRegistry.andThen(configure);
return this;
}

public DefaultModule configureScaleWriterRegistry(Consumer<ScaleWriterRegistry> configure) {
configureScaleWriterRegistry = configureScaleWriterRegistry.andThen(configure);
return this;
}

public DefaultModule configureRpcDecoderRegistry(Consumer<RpcDecoderRegistry> configure) {
configureRpcDecoderRegistry = configureRpcDecoderRegistry.andThen(configure);
return this;
}

public DefaultModule configureRpcEncoderRegistry(Consumer<RpcEncoderRegistry> configure) {
configureRpcEncoderRegistry = configureRpcEncoderRegistry.andThen(configure);
return this;
}

@Override
protected void configure() {
try {
bind(ProviderInterface.class).toInstance(providerInterface);
bind(RpcSectionFactory.class)
.toConstructor(
GeneratedRpcSectionFactory.class.getConstructor(
ProviderInterface.class,
RpcEncoderRegistry.class,
ScaleWriterRegistry.class,
RpcDecoderRegistry.class,
ScaleReaderRegistry.class))
.asEagerSingleton();
bind(PalletFactory.class)
.toConstructor(
GeneratedPalletFactory.class.getConstructor(
ScaleReaderRegistry.class,
ScaleWriterRegistry.class,
State.class
))
.asEagerSingleton();
bind(Api.class)
.toConstructor(
Api.class.getConstructor(
RpcSectionFactory.class,
PalletFactory.class))
.asEagerSingleton();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

@Provides
@Singleton
public ScaleReaderRegistry provideScaleReaderRegistry() {
val registry = new ScaleReaderRegistry();
configureScaleReaderRegistry.accept(registry);
return registry;
}

@Provides
@Singleton
kalaninja marked this conversation as resolved.
Show resolved Hide resolved
public ScaleWriterRegistry provideScaleWriterRegistry() {
val registry = new ScaleWriterRegistry();
configureScaleWriterRegistry.accept(registry);
return registry;
}

@Provides
@Singleton
public RpcDecoderRegistry provideRpcDecoderRegistry(ScaleReaderRegistry scaleReaderRegistry) {
val registry = new RpcDecoderRegistry(scaleReaderRegistry);
configureRpcDecoderRegistry.accept(registry);
return registry;
}

@Provides
@Singleton
public RpcEncoderRegistry provideRpcEncoderRegistry(ScaleWriterRegistry scaleWriterRegistry) {
val registry = new RpcEncoderRegistry(scaleWriterRegistry);
configureRpcEncoderRegistry.accept(registry);
return registry;
}

@Provides
@Singleton
public State provideState(RpcSectionFactory rpcSectionFactory) {
return rpcSectionFactory.create(State.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.strategyobject.substrateclient.api;

import com.google.inject.AbstractModule;
import com.strategyobject.substrateclient.pallet.PalletFactory;
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
import com.strategyobject.substrateclient.transport.ProviderInterface;

public class RequireModule extends AbstractModule {
@Override
protected void configure() {
requireBinding(ProviderInterface.class);
requireBinding(ScaleReaderRegistry.class);
requireBinding(ScaleWriterRegistry.class);
requireBinding(RpcDecoderRegistry.class);
requireBinding(RpcEncoderRegistry.class);
requireBinding(RpcSectionFactory.class);
requireBinding(PalletFactory.class);
requireBinding(Api.class);
}
}
Loading