Skip to content

Commit 37a717c

Browse files
committed
Implement DI with Guice (Close #22)
1 parent 7d3b515 commit 37a717c

File tree

189 files changed

+1455
-953
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+1455
-953
lines changed

api/build.gradle

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ dependencies {
77
implementation project(':scale')
88
implementation project(':transport')
99

10+
implementation 'com.google.inject:guice:5.1.0'
11+
1012
annotationProcessor project(':pallet:pallet-codegen')
1113
annotationProcessor project(':rpc:rpc-codegen')
1214

1315
testImplementation project(':tests')
1416

15-
testAnnotationProcessor project(':pallet:pallet-codegen')
16-
1717
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
18-
testImplementation 'org.testcontainers:testcontainers:1.17.1'
19-
testImplementation 'org.testcontainers:junit-jupiter:1.17.1'
18+
testImplementation 'org.testcontainers:testcontainers:1.17.2'
19+
testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
2020
testImplementation 'org.awaitility:awaitility:4.2.0'
21-
}
21+
22+
testAnnotationProcessor project(':pallet:pallet-codegen')
23+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.strategyobject.substrateclient.api;
22

3-
import com.strategyobject.substrateclient.pallet.GeneratedPalletResolver;
4-
import com.strategyobject.substrateclient.pallet.PalletResolver;
5-
import com.strategyobject.substrateclient.rpc.RpcGeneratedSectionFactory;
6-
import com.strategyobject.substrateclient.rpc.api.section.State;
3+
import com.google.inject.Guice;
4+
import com.google.inject.Injector;
5+
import com.google.inject.Module;
6+
import com.google.inject.util.Modules;
7+
import com.strategyobject.substrateclient.pallet.PalletFactory;
8+
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
79
import com.strategyobject.substrateclient.transport.ProviderInterface;
810
import lombok.NonNull;
11+
import lombok.RequiredArgsConstructor;
912
import lombok.val;
1013

1114
import java.util.Map;
@@ -16,17 +19,13 @@
1619
* It allows interacting with blockchain in various ways: using RPC's queries directly or
1720
* accessing Pallets and its APIs, such as storages, transactions, etc.
1821
*/
22+
@RequiredArgsConstructor
1923
public class Api implements AutoCloseable {
20-
private final @NonNull ProviderInterface providerInterface;
21-
private final @NonNull PalletResolver palletResolver;
24+
private final @NonNull Injector injector;
25+
private final @NonNull RpcSectionFactory rpcSectionFactory;
26+
private final @NonNull PalletFactory palletFactory;
2227
private final Map<Class<?>, Object> resolvedCache = new ConcurrentHashMap<>();
2328

24-
private Api(@NonNull ProviderInterface providerInterface) {
25-
this.providerInterface = providerInterface;
26-
27-
val state = RpcGeneratedSectionFactory.create(State.class, providerInterface);
28-
this.palletResolver = GeneratedPalletResolver.with(state);
29-
}
3029

3130
/**
3231
* Resolves the instance of a rpc by its definition.
@@ -35,9 +34,8 @@ private Api(@NonNull ProviderInterface providerInterface) {
3534
* @param <T> the type of the rpc
3635
* @return appropriate instance of the rpc
3736
*/
38-
public <T> T rpc(Class<T> clazz) {
39-
return clazz.cast(resolvedCache
40-
.computeIfAbsent(clazz, x -> RpcGeneratedSectionFactory.create(x, providerInterface)));
37+
public <T> T rpc(@NonNull Class<T> clazz) {
38+
return clazz.cast(resolvedCache.computeIfAbsent(clazz, rpcSectionFactory::create));
4139
}
4240

4341
/**
@@ -48,18 +46,26 @@ public <T> T rpc(Class<T> clazz) {
4846
* @return appropriate instance of the pallet
4947
*/
5048
public <T> T pallet(@NonNull Class<T> clazz) {
51-
return clazz.cast(resolvedCache
52-
.computeIfAbsent(clazz, palletResolver::resolve));
53-
}
54-
55-
public static Api with(ProviderInterface providerInterface) {
56-
return new Api(providerInterface);
49+
return clazz.cast(resolvedCache.computeIfAbsent(clazz, palletFactory::create));
5750
}
5851

5952
@Override
6053
public void close() throws Exception {
54+
val providerInterface = injector.getInstance(ProviderInterface.class);
6155
if (providerInterface instanceof AutoCloseable) {
6256
((AutoCloseable) providerInterface).close();
6357
}
6458
}
59+
60+
public static ApiBuilder with(@NonNull ProviderInterface providerInterface) {
61+
return new ApiBuilder(Guice.createInjector(new DefaultModule(providerInterface)));
62+
}
63+
64+
public static ApiBuilder with(@NonNull ProviderInterface providerInterface, @NonNull Module overrideModule) {
65+
val module = Modules
66+
.override(new DefaultModule(providerInterface))
67+
.with(overrideModule);
68+
69+
return new ApiBuilder(Guice.createInjector(module));
70+
}
6571
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.google.inject.Injector;
4+
import com.strategyobject.substrateclient.common.types.AutoRegistry;
5+
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
6+
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
7+
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
8+
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
9+
import com.strategyobject.substrateclient.transport.ProviderInterface;
10+
import lombok.val;
11+
12+
import java.util.concurrent.CompletableFuture;
13+
import java.util.function.Consumer;
14+
15+
public class ApiBuilder {
16+
private static final String ROOT_PREFIX = "com.strategyobject.substrateclient";
17+
18+
private final Injector injector;
19+
20+
public ApiBuilder(Injector injector) {
21+
this.injector = injector;
22+
23+
autoRegister(
24+
ScaleReaderRegistry.class,
25+
ScaleWriterRegistry.class,
26+
RpcDecoderRegistry.class,
27+
RpcEncoderRegistry.class);
28+
}
29+
30+
public <T> ApiBuilder configure(Class<T> clazz, Consumer<T> consumer) {
31+
consumer.accept(injector.getInstance(clazz));
32+
return this;
33+
}
34+
35+
public CompletableFuture<Api> build() {
36+
val provider = injector.getInstance(ProviderInterface.class);
37+
val result = provider.isConnected() ?
38+
CompletableFuture.<Void>completedFuture(null) :
39+
provider.connect();
40+
41+
return result.thenApply(ignored -> injector.getInstance(Api.class));
42+
}
43+
44+
@SafeVarargs
45+
private final void autoRegister(Class<? extends AutoRegistry>... registries) {
46+
for (val registry : registries) {
47+
injector.getInstance(registry).registerAnnotatedFrom(ROOT_PREFIX);
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.google.inject.Injector;
5+
import com.google.inject.Provides;
6+
import com.google.inject.Singleton;
7+
import com.strategyobject.substrateclient.pallet.GeneratedPalletFactory;
8+
import com.strategyobject.substrateclient.pallet.PalletFactory;
9+
import com.strategyobject.substrateclient.rpc.GeneratedRpcSectionFactory;
10+
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
11+
import com.strategyobject.substrateclient.rpc.api.section.State;
12+
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
13+
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
14+
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
15+
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
16+
import com.strategyobject.substrateclient.transport.ProviderInterface;
17+
import lombok.NonNull;
18+
import lombok.RequiredArgsConstructor;
19+
20+
@RequiredArgsConstructor
21+
public class DefaultModule extends AbstractModule {
22+
private final @NonNull ProviderInterface providerInterface;
23+
24+
@Override
25+
protected void configure() {
26+
try {
27+
bind(ProviderInterface.class).toInstance(providerInterface);
28+
bind(ScaleReaderRegistry.class).asEagerSingleton();
29+
bind(ScaleWriterRegistry.class).asEagerSingleton();
30+
bind(RpcDecoderRegistry.class)
31+
.toConstructor(RpcDecoderRegistry.class.getConstructor(ScaleReaderRegistry.class))
32+
.asEagerSingleton();
33+
bind(RpcEncoderRegistry.class)
34+
.toConstructor(RpcEncoderRegistry.class.getConstructor(ScaleWriterRegistry.class))
35+
.asEagerSingleton();
36+
bind(RpcSectionFactory.class)
37+
.toConstructor(
38+
GeneratedRpcSectionFactory.class.getConstructor(
39+
ProviderInterface.class,
40+
RpcEncoderRegistry.class,
41+
ScaleWriterRegistry.class,
42+
RpcDecoderRegistry.class,
43+
ScaleReaderRegistry.class))
44+
.asEagerSingleton();
45+
bind(PalletFactory.class)
46+
.toConstructor(
47+
GeneratedPalletFactory.class.getConstructor(
48+
ScaleReaderRegistry.class,
49+
ScaleWriterRegistry.class,
50+
State.class
51+
))
52+
.asEagerSingleton();
53+
bind(Api.class)
54+
.toConstructor(
55+
Api.class.getConstructor(
56+
Injector.class,
57+
RpcSectionFactory.class,
58+
PalletFactory.class))
59+
.asEagerSingleton();
60+
} catch (NoSuchMethodException e) {
61+
throw new RuntimeException(e);
62+
}
63+
}
64+
65+
@Singleton
66+
@Provides
67+
public State provideState(RpcSectionFactory rpcSectionFactory) {
68+
return rpcSectionFactory.create(State.class);
69+
}
70+
}

api/src/test/java/com/strategyobject/substrateclient/api/ApiTests.java

+38-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.strategyobject.substrateclient.api;
22

3+
import com.strategyobject.substrateclient.common.strings.HexConverter;
4+
import com.strategyobject.substrateclient.pallet.PalletFactory;
5+
import com.strategyobject.substrateclient.rpc.api.AccountId;
36
import com.strategyobject.substrateclient.rpc.api.BlockNumber;
7+
import com.strategyobject.substrateclient.rpc.api.Index;
8+
import com.strategyobject.substrateclient.rpc.api.section.System;
49
import com.strategyobject.substrateclient.tests.containers.SubstrateVersion;
510
import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer;
611
import com.strategyobject.substrateclient.transport.ws.WsProvider;
@@ -12,8 +17,7 @@
1217
import java.math.BigInteger;
1318
import java.util.concurrent.TimeUnit;
1419

15-
import static org.junit.jupiter.api.Assertions.assertNotEquals;
16-
import static org.junit.jupiter.api.Assertions.assertNotNull;
20+
import static org.junit.jupiter.api.Assertions.*;
1721

1822
@Testcontainers
1923
class ApiTests {
@@ -29,15 +33,45 @@ void getSystemPalletAndCall() throws Exception { // TODO move the test out of th
2933
.build();
3034
wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS);
3135

32-
try (val api = Api.with(wsProvider)) {
36+
try (val api = Api.with(wsProvider).build().join()) {
3337
val systemPallet = api.pallet(SystemPallet.class);
3438
val blockHash = systemPallet
3539
.blockHash()
3640
.get(BlockNumber.GENESIS)
3741
.get(WAIT_TIMEOUT, TimeUnit.SECONDS);
3842

3943
assertNotNull(blockHash);
40-
assertNotEquals(BigInteger.ZERO, new BigInteger(blockHash.getData()));
44+
assertNotEquals(BigInteger.ZERO, new BigInteger(blockHash.getBytes()));
45+
}
46+
}
47+
48+
@Test
49+
void getSystemSectionAndCall() throws Exception {
50+
val wsProvider = WsProvider.builder()
51+
.setEndpoint(substrate.getWsAddress())
52+
.build();
53+
54+
try (val api = Api.with(wsProvider).build().join()) {
55+
val system = api.rpc(System.class);
56+
val alicePublicKey = HexConverter.toBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
57+
val actual = system.accountNextIndex(AccountId.fromBytes(alicePublicKey)).join();
58+
59+
assertEquals(Index.ZERO, actual);
60+
}
61+
}
62+
63+
@Test
64+
void reconfigureApi() throws Exception {
65+
val wsProvider = WsProvider.builder()
66+
.setEndpoint(substrate.getWsAddress())
67+
.build();
68+
69+
val expected = "expected";
70+
try (val api = Api.with(wsProvider, new OverrideModule())
71+
.configure(PalletFactory.class, x -> ((ThrowingPalletFactory) x).setMessage(expected))
72+
.build()
73+
.join()) {
74+
assertThrows(RuntimeException.class, () -> api.pallet(SystemPallet.class), expected);
4175
}
4276
}
4377
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.strategyobject.substrateclient.pallet.PalletFactory;
5+
6+
public class OverrideModule extends AbstractModule {
7+
@Override
8+
protected void configure() {
9+
bind(PalletFactory.class).to(ThrowingPalletFactory.class);
10+
}
11+
}

api/src/test/java/com/strategyobject/substrateclient/api/SystemPallet.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
@Pallet("System")
1313
public interface SystemPallet {
1414
@Storage(
15-
value = "BlockHash",
15+
name = "BlockHash",
1616
keys = {
1717
@StorageKey(
1818
type = @Scale(BlockNumber.class),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.strategyobject.substrateclient.pallet.PalletFactory;
4+
import lombok.NonNull;
5+
import lombok.Setter;
6+
7+
public class ThrowingPalletFactory implements PalletFactory {
8+
@Setter
9+
private String message;
10+
11+
@Override
12+
public <T> T create(@NonNull Class<T> interfaceClass) {
13+
throw new RuntimeException(message);
14+
}
15+
}

build.gradle

+7-7
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ subprojects {
2323
sourceCompatibility = '1.8'
2424

2525
dependencies {
26-
implementation 'com.google.guava:guava:30.1.1-jre'
27-
implementation 'org.slf4j:slf4j-api:1.7.32'
26+
implementation 'com.google.guava:guava:31.1-jre'
27+
implementation 'org.slf4j:slf4j-api:1.7.36'
2828

2929
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
3030
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
31-
testImplementation 'org.assertj:assertj-core:3.22.0'
32-
testImplementation 'org.mockito:mockito-core:3.12.4'
33-
testImplementation 'org.mockito:mockito-inline:3.12.4'
34-
testImplementation 'org.slf4j:slf4j-simple:1.7.32'
31+
testImplementation 'org.assertj:assertj-core:3.23.1'
32+
testImplementation 'org.mockito:mockito-core:4.6.1'
33+
testImplementation 'org.mockito:mockito-inline:4.6.1'
34+
testImplementation 'org.slf4j:slf4j-simple:1.7.36'
3535

36-
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
36+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
3737
}
3838

3939
test {

common/src/main/java/com/strategyobject/substrateclient/common/codegen/AnnotationUtils.java

+15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import javax.lang.model.AnnotatedConstruct;
99
import javax.lang.model.element.AnnotationMirror;
1010
import java.lang.annotation.Annotation;
11+
import java.util.Arrays;
1112
import java.util.stream.Collectors;
1213
import java.util.stream.IntStream;
1314

@@ -57,6 +58,20 @@ public static AnnotationSpec suppressWarnings(String... warnings) {
5758
.build();
5859
}
5960

61+
public static boolean isAnnotatedWith(AnnotatedConstruct annotated, Class<? extends Annotation> annotation) {
62+
return annotated.getAnnotation(annotation) != null;
63+
}
64+
65+
@SafeVarargs
66+
public static boolean isAnnotatedWithAny(AnnotatedConstruct annotated, Class<? extends Annotation>... annotations) {
67+
return Arrays.stream(annotations).anyMatch(x -> annotated.getAnnotation(x) != null);
68+
}
69+
70+
@SafeVarargs
71+
public static boolean isAnnotatedWithAll(AnnotatedConstruct annotated, Class<? extends Annotation>... annotations) {
72+
return Arrays.stream(annotations).allMatch(x -> annotated.getAnnotation(x) != null);
73+
}
74+
6075
private AnnotationUtils() {
6176
}
6277
}

0 commit comments

Comments
 (0)