© 2019-2022 rxmicro.io.
Free use of this software is granted under the terms of the Apache License 2.0
.
Copies of this entity may be made for Your own use and for distribution to others, provided that You do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. If You find errors or omissions in this entity, please don’t hesitate to submit an issue or open a pull request with a fix. |
The RxMicro framework supports creation of dynamic repositories for interaction with databases.
To interact with Mongo DB
, the RxMicro framework provides the rxmicro.data.mongo
module.
1. Basic Usage
To use the rxmicro.data.mongo
module in the project, the following two steps must be taken:
-
Inject the
rxmicro.data.mongo
dependency to thepom.xml
file:
<dependency>
<groupId>io.rxmicro</groupId>
<artifactId>rxmicro-data-mongo</artifactId>
<version>${rxmicro.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId> (1)
<version>${projectreactor.version}</version>
</dependency>
1 | Besides the rxmicro.data.mongo dependency, it is also recommended to add the reactive programming library. |
Instead of adding a third-party reactive programming library, You can also use the java.util.concurrent library built into the JDK, but often in practice the java.util.concurrent library’s features are not enough. Therefore, it is recommended to use the Project Reactor or the RxJava library! |
-
Add the
rxmicro.data.mongo
module to themodule-info.java
descriptor:
module examples.data.mongo.basic {
requires rxmicro.data.mongo;
requires reactor.core; (1)
}
1 | When using a third-party reactive programming library, do not forget to add the corresponding module. |
Once the rxmicro.data.mongo
module is added, You can create a data model class and a dynamic repository:
public final class Account {
String firstName;
String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
(1)
@MongoRepository(collection = "account")
public interface DataRepository {
(2)
@Find(query = "{email: ?}")
Mono<Account> findByEmail(String email);
}
1 | In order for a standard interface to be recognized by the RxMicro framework as a dynamic repository for interaction with Mongo DB , this interface should be annotated with the @MongoRepository annotation. |
2 | The dynamic repository may contain methods that form a query to the Mongo DB .
(The query that used for a request for data uses the JSON format (Specialized request format for Mongo DB ) and is specified in the annotation parameters.
For each operation supported by Mongo DB , the RxMicro framework defines a separate annotation:
The @Find annotation describes the
db.collection.find() operation.) |
Since the dynamic repository is a RxMicro component, for its testing You need to use the microservice component testing approach:
The common approach recommended for testing dynamic repositories, that interact with |
@Testcontainers
@RxMicroComponentTest(DataRepository.class)
final class DataRepositoryTest {
@Container
private final GenericContainer<?> mongoTestDb =
new GenericContainer<>("rxmicro/mongo-test-db")
.withExposedPorts(27017);
@WithConfig
private final MongoConfig mongoConfig = new MongoConfig()
.setDatabase("rxmicro");
private DataRepository dataRepository;
@BeforeEach
void beforeEach() {
mongoConfig
.setHost(mongoTestDb.getHost())
.setPort(mongoTestDb.getFirstMappedPort());
}
@Test
void Should_find_account() {
final Account account =
dataRepository.findByEmail("richard.hendricks@piedpiper.com")
.blockOptional()
.orElseThrow();
assertEquals("Richard", account.getFirstName());
assertEquals("Hendricks", account.getLastName());
}
}
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
2. RxMicro Annotations
The RxMicro framework supports the following RxMicro Annotations
:
Annotation | Description |
---|---|
Sets mapping between the field name in the (By default, the RxMicro framework uses the Java model class field name as the field name in the |
|
Sets the strategy of field name formation in the (If this annotation annotates the Java model class, then the set strategy will be used for all fields in this class.
For example, if You set the default |
|
Allows You to configure the repository generation process. |
|
Allows setting mapping between one method parameter marked with this annotation and several universal placeholders that are used in the request to |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a repository method that must execute a |
|
Denotes a model field that must be used as document unique identifier. |
|
Denotes that an interface is a dynamic generated Mongo data repository. |
|
Denotes an abstract class that contains a partial implementation of the annotated by this annotation a Mongo Data Repository interface. |
3. Repositories Testing
For successful functional testing of dynamic repositories, that interact with Mongo DB
, it is required:
-
Presence of a script that creates a test database.
-
Mechanism for preparing a database for testing: creating a database before starting the test and deleting a database after completing the test.
3.1. Test Database
A test database was created for testing the rxmicro.data.mongo
, module features, which are described in this section.
The test database consists of one account
collection, which contains 6 documents describing the accounts of the test users:
Scripts for creating a test database are available at the following link: |
The following classes of Java models correspond to the documents created in the test database:
public class Account {
@DocumentId
Long id;
String email;
String firstName;
String lastName;
BigDecimal balance;
Role role;
}
public enum Role {
CEO,
Systems_Architect,
Lead_Engineer,
Engineer
}
For ease of studying the The source code of the project used as a base for building this |
3.2. Test Templates
As a mechanism for preparing a database for testing (creating a database before starting the test and deleting a database after completing the test), it is most convenient to use docker
.
To start docker
containers in the functional test it is convenient to use the Testcontainers
Java library:
(1)
@Testcontainers
(2)
@RxMicroComponentTest(DataRepository.class)
final class DataRepositoryTestTemplate1 {
(3)
@Container
private static final GenericContainer<?> MONGO_TEST_DB =
new GenericContainer<>("rxmicro/mongo-test-db")
.withExposedPorts(27017); (4)
(5)
@WithConfig
private static final MongoConfig MONGO_CONFIG = new MongoConfig()
.setDatabase("rxmicro"); (6)
@BeforeAll
static void beforeAll() {
MONGO_TEST_DB.start(); (7)
MONGO_CONFIG
.setHost(MONGO_TEST_DB.getHost()) (8)
.setPort(MONGO_TEST_DB.getFirstMappedPort());
}
private DataRepository dataRepository; (9)
// ... test methods must be here
@AfterAll
static void afterAll() {
MONGO_TEST_DB.stop(); (10)
}
}
1 | The @Testcontainers
annotation activates the start and stop of the docker containers to be used in this test. |
2 | Since the dynamic repository is a RxMicro component, for its testing You need to use the microservice component testing approach. |
3 | The @Container
annotation indicates the docker container that will be used in this test.
As an image on the basis of which it is necessary to create the docker container, the Mongo DB ready-made image with the
rxmicro/mongo-test-db test database is used. |
4 | When starting the docker container, You need to open the standard port for Mongo DB . |
5 | Using the @WithConfig
annotation, the configuration available only during the test is declared. |
6 | Setting up the configuration to interact with the test database. |
7 | Before running all tests, You must start the docker container. |
8 | After starting the docker container, You need to read the random IP address and port that will be used when connecting to the running docker container. |
9 | When testing microservice components, it is necessary to specify a reference to the component in which the RxMicro framework will inject the tested component. |
10 | After completing all the tests, You must stop the docker container. |
The main advantage of this template is the speed of testing.
Since the docker
container is created once before starting all test methods, the total runtime of all test methods is reduced.
The main disadvantage of this template is that if any test method changes the Mongo DB
state, the following test method may end with an error.
Therefore, this functional test template should be used for queries to Mongo DB
that do not change the database state!
If You need to test methods that change the Mongo DB
state, You should use another template:
(1)
@Testcontainers
(2)
@RxMicroComponentTest(DataRepository.class)
final class DataRepositoryTestTemplate2 {
(3)
@Container
private final GenericContainer<?> mongoTestDb =
new GenericContainer<>("rxmicro/mongo-test-db")
.withExposedPorts(27017); (4)
(5)
@WithConfig
private final MongoConfig mongoConfig = new MongoConfig()
.setDatabase("rxmicro"); (6)
private DataRepository dataRepository; (7)
@BeforeEach
void beforeEach() {
mongoConfig
.setHost(mongoTestDb.getHost()) (8)
.setPort(mongoTestDb.getFirstMappedPort());
}
// ... test methods must be here
}
1 | The @Testcontainers
annotation activates the start and stop of the docker containers to be used in this test. |
2 | Since the dynamic repository is a RxMicro component, for its testing You need to use the microservice component testing approach |
3 | The @Container
annotation indicates the docker container that will be used in this test.
As an image on the basis of which it is necessary to create the docker container, the Mongo DB ready-made image with the
rxmicro/mongo-test-db test database is used. |
4 | When starting the docker container, You need to open the standard port for Mongo DB . |
5 | Using the @WithConfig
annotation, the configuration available only during the test is declared. |
6 | Setting up the configuration to interact with the test database. |
7 | When testing microservice components, it is necessary to specify a reference to the component in which the RxMicro framework will inject the tested component. |
8 | After starting the docker container, You need to read the random IP address and port that will be used when connecting to the running docker container. |
This template for each test method will create and drop the docker
container, which may increase the total runtime of all test methods.
Therefore, select the most appropriate functional test template based on the requirements of the tested functionality!
The So You should start and stop the |
4. Universal Placeholder
The RxMicro framework recommends using the universal placeholder (?
) as parameter value placeholder in the Mongo DB
queries:
@Find(query = "{email: ?}")
Mono<Account> findByEmail(String email);
If this method invoked with the following parameter: the RxMicro framework will generate the following |
5. @RepeatParameter
Annotation
The universal placeholder (?
) is the simplest type of placeholders.
But unfortunately, it has one disadvantage: if a query parameter must be repeated, a developer must define a copy of this parameter:
@Find(query = "{$or: [{firstName: ?}, {lastName: ?}]}")
Mono<Account> findByFirstOrLastNames(String name1, String name2);
The @RepeatParameter
annotation fixes this disadvantage.
The following code is an equivalent to the code with a copy of the name
parameter:
@Find(query = "{$or: [{firstName: ?}, {lastName: ?}]}")
Mono<Account> findByFirstOrLastNames(@RepeatParameter(2) String name);
6. Mongo Operations
6.1. @Find
The rxmicro.data.mongo
module supports the db.collection.find()
operation:
@Find(query = "{_id: ?}")
Mono<Account> findById(long id);
@Find(query = "{_id: ?}", projection = "{firstName: 1, lastName: 1}")
Mono<Account> findWithProjectionById(long id);
@Find(query = "{role: ?}", sort = "{role: 1, balance: 1}")
Flux<Account> findByRole(Role role, Pageable pageable);
@Find(query = "{role: ?}", sort = "{role: ?, balance: ?}")
Flux<Account> findByRole(Role role, @RepeatParameter(2) SortOrder sortOrder, Pageable pageable);
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.2. @Aggregate
The rxmicro.data.mongo
module supports the db.collection.aggregate()
operation:
public final class Report {
@DocumentId
Role id;
BigDecimal total;
}
@Aggregate(pipeline = {
"{ $group : { _id: '$role', total : { $sum: '$balance'}} }",
"{ $sort: {total: -1, _id: -1} }"
})
Flux<Report> aggregate();
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.3. @Distinct
The rxmicro.data.mongo
module supports the db.collection.distinct()
operation:
@Distinct(field = "email", query = "{_id: ?}")
Mono<String> getEmailById(long id);
@Distinct(field = "role")
Flux<Role> getAllUsedRoles();
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.4. @CountDocuments
The rxmicro.data.mongo
module supports the db.collection.countDocuments()
operation:
@CountDocuments
Mono<Long> countDocuments();
@CountDocuments(query = "{role:?}", skip = 0, limit = 100)
Mono<Long> countDocuments(Role role);
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.5. @EstimatedDocumentCount
The rxmicro.data.mongo
module supports the db.collection.estimatedDocumentCount()
operation:
@EstimatedDocumentCount
Mono<Long> estimatedDocumentCount();
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.6. @Insert
The rxmicro.data.mongo
module supports the db.collection.insertOne()
operation:
@Insert
Mono<Void> insert(Account account);
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.7. @Update
The rxmicro.data.mongo
module supports the db.collection.updateOne()
operation:
@Update
Mono<Boolean> updateEntity(AccountEntity accountEntity);
@Update(filter = "{_id: ?}")
Mono<Void> updateDocument(AccountDocument accountDocument, long id);
@Update(update = "{$set: {balance: ?}}", filter = "{_id: ?}")
Mono<Long> updateById(BigDecimal balance, long id);
@Update(update = "{$set: {balance: ?}}", filter = "{role: ?}")
Mono<UpdateResult> updateByRole(BigDecimal balance, Role role);
@Update(update = "{$set: {balance: ?}}")
Mono<UpdateResult> updateAll(BigDecimal balance);
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6.8. @Delete
The rxmicro.data.mongo
module supports the db.collection.deleteOne()
and db.collection.deleteMany()
operations:
@Delete
Mono<Boolean> deleteById(Long id);
@Delete(filter = "{role: ?}")
Mono<Long> deleteByRole(Role role);
For more information, we recommend that You familiarize yourself with the following examples: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
7. Partial Implementation
If the Mongo data repository generated by the RxMicro Annotation Processor
contains errors, incorrect or non-optimized logic, the developer can use the Partial Implementation
feature.
This feature allows You to implement methods for the Mongo data repository on Your own, instead of generating them by the RxMicro framework.
To activate this feature, You need to use the
@PartialImplementation
annotation, and specify an abstract class that contains a partial implementation of method(s) for Mongo data repository:
@MongoRepository(collection = DataRepository.COLLECTION_NAME)
(1)
@PartialImplementation(AbstractDataRepository.class)
public interface DataRepository {
String COLLECTION_NAME = "account";
@CountDocuments
CompletableFuture<Long> generatedMethod();
CompletableFuture<Long> userDefinedMethod();
}
1 | Using the
@PartialImplementation
annotation, the AbstractDataRepository class is specified. |
An AbstractDataRepository
contains the following content:
public abstract class AbstractDataRepository extends AbstractMongoRepository
implements DataRepository {
protected AbstractDataRepository(final Class<?> repositoryClass,
final MongoCollection<Document> collection) {
super(repositoryClass, collection);
}
@Override
public CompletableFuture<Long> userDefinedMethod() {
return CompletableFuture.completedFuture(100L);
}
}
An abstract class that contains a partial implementation must meet the following requirements:
-
The class must be an
abstract
one. -
The class must extend the
AbstractMongoRepository
one. -
The class must implement the Mongo data repository interface.
-
The class must contain an implementation of all methods that are not generated automatically.
In terms of infrastructure, the repository methods generated and defined by the developer for Mongo data repository do not differ:
@Test
void generatedMethod() {
assertEquals(6L, dataRepository.generatedMethod().join());
}
@Test
void userDefinedMethod() {
assertEquals(100L, dataRepository.userDefinedMethod().join());
}
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
8. Logging
Mongo Data Repositories use the MongoDB Async Driver, so in order to activate database request logging to the Mongo DB, You must configure the MongoDB Async Driver Logger:
For example, if to the classpath
of the current project add the jul.properties
resource:
org.mongodb.driver.protocol.level=TRACE
,then Mongo Data Repositories will generate request logs to the Mongo DB while working:
2020-03-08 13:15:03.912 [DEBUG] org.mongodb.driver.protocol.command : Sending command '{"find": "account", "filter": {"_id": 1}, "batchSize": 2147483647, "$db": "rxmicro"}' with request id 6 to database rxmicro on connection [connectionId{localValue:2, serverValue:4}] to server localhost:27017
2020-03-08 13:15:03.914 [DEBUG] org.mongodb.driver.protocol.command : Execution of command with request id 6 completed successfully in 3.11 ms on connection [connectionId{localValue:2, serverValue:4}] to server localhost:27017