Skip to content

Commit 7032244

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

File tree

191 files changed

+1498
-985
lines changed

Some content is hidden

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

191 files changed

+1498
-985
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,32 +1,28 @@
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.Module;
5+
import com.strategyobject.substrateclient.pallet.PalletFactory;
6+
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
77
import com.strategyobject.substrateclient.transport.ProviderInterface;
88
import lombok.NonNull;
9-
import lombok.val;
9+
import lombok.RequiredArgsConstructor;
1010

1111
import java.util.Map;
1212
import java.util.concurrent.ConcurrentHashMap;
13+
import java.util.function.Supplier;
1314

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

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-
}
3026

3127
/**
3228
* Resolves the instance of a rpc by its definition.
@@ -35,9 +31,8 @@ private Api(@NonNull ProviderInterface providerInterface) {
3531
* @param <T> the type of the rpc
3632
* @return appropriate instance of the rpc
3733
*/
38-
public <T> T rpc(Class<T> clazz) {
39-
return clazz.cast(resolvedCache
40-
.computeIfAbsent(clazz, x -> RpcGeneratedSectionFactory.create(x, providerInterface)));
34+
public <T> T rpc(@NonNull Class<T> clazz) {
35+
return clazz.cast(resolvedCache.computeIfAbsent(clazz, rpcSectionFactory::create));
4136
}
4237

4338
/**
@@ -48,18 +43,21 @@ public <T> T rpc(Class<T> clazz) {
4843
* @return appropriate instance of the pallet
4944
*/
5045
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);
46+
return clazz.cast(resolvedCache.computeIfAbsent(clazz, palletFactory::create));
5747
}
5848

5949
@Override
6050
public void close() throws Exception {
61-
if (providerInterface instanceof AutoCloseable) {
62-
((AutoCloseable) providerInterface).close();
51+
if (rpcSectionFactory instanceof AutoCloseable) {
52+
((AutoCloseable) rpcSectionFactory).close();
6353
}
6454
}
55+
56+
public static ApiBuilder with(@NonNull Supplier<ProviderInterface> providerInterface) {
57+
return with(new DefaultModule(providerInterface.get()));
58+
}
59+
60+
public static ApiBuilder with(@NonNull Module module) {
61+
return new ApiBuilder(Guice.createInjector(new RequireModule(), module));
62+
}
6563
}
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,68 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.google.inject.Provides;
5+
import com.google.inject.Singleton;
6+
import com.strategyobject.substrateclient.pallet.GeneratedPalletFactory;
7+
import com.strategyobject.substrateclient.pallet.PalletFactory;
8+
import com.strategyobject.substrateclient.rpc.GeneratedRpcSectionFactory;
9+
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
10+
import com.strategyobject.substrateclient.rpc.api.section.State;
11+
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
12+
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
13+
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
14+
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
15+
import com.strategyobject.substrateclient.transport.ProviderInterface;
16+
import lombok.NonNull;
17+
import lombok.RequiredArgsConstructor;
18+
19+
@RequiredArgsConstructor
20+
public class DefaultModule extends AbstractModule {
21+
private final @NonNull ProviderInterface providerInterface;
22+
23+
@Override
24+
protected void configure() {
25+
try {
26+
bind(ProviderInterface.class).toInstance(providerInterface);
27+
bind(ScaleReaderRegistry.class).asEagerSingleton();
28+
bind(ScaleWriterRegistry.class).asEagerSingleton();
29+
bind(RpcDecoderRegistry.class)
30+
.toConstructor(RpcDecoderRegistry.class.getConstructor(ScaleReaderRegistry.class))
31+
.asEagerSingleton();
32+
bind(RpcEncoderRegistry.class)
33+
.toConstructor(RpcEncoderRegistry.class.getConstructor(ScaleWriterRegistry.class))
34+
.asEagerSingleton();
35+
bind(RpcSectionFactory.class)
36+
.toConstructor(
37+
GeneratedRpcSectionFactory.class.getConstructor(
38+
ProviderInterface.class,
39+
RpcEncoderRegistry.class,
40+
ScaleWriterRegistry.class,
41+
RpcDecoderRegistry.class,
42+
ScaleReaderRegistry.class))
43+
.asEagerSingleton();
44+
bind(PalletFactory.class)
45+
.toConstructor(
46+
GeneratedPalletFactory.class.getConstructor(
47+
ScaleReaderRegistry.class,
48+
ScaleWriterRegistry.class,
49+
State.class
50+
))
51+
.asEagerSingleton();
52+
bind(Api.class)
53+
.toConstructor(
54+
Api.class.getConstructor(
55+
RpcSectionFactory.class,
56+
PalletFactory.class))
57+
.asEagerSingleton();
58+
} catch (NoSuchMethodException e) {
59+
throw new RuntimeException(e);
60+
}
61+
}
62+
63+
@Singleton
64+
@Provides
65+
public State provideState(RpcSectionFactory rpcSectionFactory) {
66+
return rpcSectionFactory.create(State.class);
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.strategyobject.substrateclient.pallet.PalletFactory;
5+
import com.strategyobject.substrateclient.rpc.RpcSectionFactory;
6+
import com.strategyobject.substrateclient.rpc.registries.RpcDecoderRegistry;
7+
import com.strategyobject.substrateclient.rpc.registries.RpcEncoderRegistry;
8+
import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry;
9+
import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry;
10+
import com.strategyobject.substrateclient.transport.ProviderInterface;
11+
12+
public class RequireModule extends AbstractModule {
13+
@Override
14+
protected void configure() {
15+
requireBinding(ProviderInterface.class);
16+
requireBinding(ScaleReaderRegistry.class);
17+
requireBinding(ScaleWriterRegistry.class);
18+
requireBinding(RpcDecoderRegistry.class);
19+
requireBinding(RpcEncoderRegistry.class);
20+
requireBinding(RpcSectionFactory.class);
21+
requireBinding(PalletFactory.class);
22+
requireBinding(Api.class);
23+
}
24+
}

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

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

3+
import com.google.inject.CreationException;
4+
import com.google.inject.util.Modules;
5+
import com.strategyobject.substrateclient.common.strings.HexConverter;
6+
import com.strategyobject.substrateclient.pallet.PalletFactory;
7+
import com.strategyobject.substrateclient.rpc.api.AccountId;
38
import com.strategyobject.substrateclient.rpc.api.BlockNumber;
9+
import com.strategyobject.substrateclient.rpc.api.Index;
10+
import com.strategyobject.substrateclient.rpc.api.section.System;
411
import com.strategyobject.substrateclient.tests.containers.SubstrateVersion;
512
import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer;
613
import com.strategyobject.substrateclient.transport.ws.WsProvider;
@@ -12,8 +19,7 @@
1219
import java.math.BigInteger;
1320
import java.util.concurrent.TimeUnit;
1421

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

1824
@Testcontainers
1925
class ApiTests {
@@ -25,19 +31,54 @@ class ApiTests {
2531
@Test
2632
void getSystemPalletAndCall() throws Exception { // TODO move the test out of the project
2733
val wsProvider = WsProvider.builder()
28-
.setEndpoint(substrate.getWsAddress())
29-
.build();
30-
wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS);
34+
.setEndpoint(substrate.getWsAddress());
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+
53+
try (val api = Api.with(wsProvider).build().join()) {
54+
val system = api.rpc(System.class);
55+
val alicePublicKey = HexConverter.toBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
56+
val actual = system.accountNextIndex(AccountId.fromBytes(alicePublicKey)).join();
57+
58+
assertEquals(Index.ZERO, actual);
59+
}
60+
}
61+
62+
@Test
63+
void reconfigureApi() throws Exception {
64+
val wsProvider = WsProvider.builder()
65+
.setEndpoint(substrate.getWsAddress())
66+
.build();
67+
68+
val expected = "expected";
69+
val module = Modules.override(new DefaultModule(wsProvider)).with(new OverrideModule());
70+
try (val api = Api.with(module)
71+
.configure(PalletFactory.class, x -> ((ThrowingPalletFactory) x).setMessage(expected))
72+
.build()
73+
.join()) {
74+
assertThrows(RuntimeException.class, () -> api.pallet(SystemPallet.class), expected);
4175
}
4276
}
77+
78+
@Test
79+
void validateModule() {
80+
assertThrows(CreationException.class, () -> Api.with(binder -> {
81+
// no bindings provided
82+
}));
83+
}
4384
}
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 {

0 commit comments

Comments
 (0)