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

Mongo sync guide #528

Merged
merged 12 commits into from Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,51 @@
package example.micronaut;

import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import javax.validation.constraints.NotBlank;

@Introspected // <1>
public class Fruit {

@NonNull
@NotBlank // <2>
private String name;
sdelamo marked this conversation as resolved.
Show resolved Hide resolved

@Nullable
private String description;

public Fruit() {

}

public Fruit(@NonNull String name) {
this(name, null);
}

@Creator
public Fruit(@NonNull String name, @Nullable String description) {
this.name = name;
this.description = description;
}

public void setName(@NonNull String name) {
this.name = name;
}

public void setDescription(@Nullable String description) {
this.description = description;
}

@NonNull
public String getName() {
return name;
}

@Nullable
public String getDescription() {
return description;
}

}
@@ -0,0 +1,40 @@
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Status;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Controller("/fruits") // <1>
@ExecuteOn(TaskExecutors.IO) // <2>
public class FruitController {

private final FruitRepository fruitService;

public FruitController(FruitRepository fruitService) { // <3>
this.fruitService = fruitService;
}

@Get // <4>
public List<Fruit> list() {
return StreamSupport.stream(fruitService.list().spliterator(), false)
.collect(Collectors.toList());
}

@Post // <5>
@Status(HttpStatus.CREATED) // <6>
public void save(@NonNull @NotNull @Valid Fruit fruit) { // <7>
fruitService.save(fruit);
}
}
@@ -0,0 +1,13 @@
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Iterator;

public interface FruitRepository {
@NonNull
Iterable<Fruit> list();

void save(@NonNull @NotNull @Valid Fruit fruit); // <1>
}
@@ -0,0 +1,11 @@
package example.micronaut;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.Named;

@ConfigurationProperties("db") // <1>
public interface MongoDbConfiguration extends Named {
@NonNull
String getCollection();
}
@@ -0,0 +1,46 @@
package example.micronaut;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
import org.bson.Document;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Singleton // <1>
public class MongoDbFruitRepository implements FruitRepository {

private final MongoDbConfiguration mongoConf;
private final MongoClient mongoClient;

public MongoDbFruitRepository(MongoDbConfiguration mongoConf, // <2>
MongoClient mongoClient) { // <3>
this.mongoConf = mongoConf;
this.mongoClient = mongoClient;
}

@Override
public void save(@NonNull @NotNull @Valid Fruit fruit){
getCollection().insertOne(fruit);
}
Comment on lines +25 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is possible or not but I think it would be better if you add an id field to Fruit (probably an UUID) and configure the POJO (or Mongo) to assign it automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to have different POJOs. I would like to use the same POJO for the controller layer and the persistence layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could do this as an improvement after the guide is published.


@Override
@NonNull
public Iterable<Fruit> list() {
return getCollection().find();
}

@NonNull
private MongoCollection<Fruit> getCollection(){
return mongoClient.getDatabase(mongoConf.getName())
.getCollection(mongoConf.getCollection(), Fruit.class);
}
}
@@ -0,0 +1,22 @@
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.client.annotation.Client;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;

@Client("/fruits")
public interface FruitClient {

@Post
@NonNull
HttpStatus save(@NonNull @NotNull @Valid Fruit fruit);

@NonNull
@Get
List<Fruit> findAll();
}
@@ -0,0 +1,51 @@
package example.micronaut;

import io.micronaut.context.ApplicationContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Collections;
import java.util.List;

public class FruitControllerTest {

@Test
void fruitsEndpointInteractsWithMongo() {
EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer.class,
Collections.singletonMap("mongodb.uri", MongoDbUtils.getMongoDbUri()));
FruitClient fruitClient = embeddedServer.getApplicationContext()
.getBean(FruitClient.class);

List<Fruit> fruits = fruitClient.findAll();
assertTrue(fruits.isEmpty());

HttpStatus status = fruitClient.save(new Fruit("banana"));

assertEquals(HttpStatus.CREATED, status);
fruits = fruitClient.findAll();
assertFalse(fruits.isEmpty());
assertEquals("banana", fruits.get(0).getName());
assertNull(fruits.get(0).getDescription());

status = fruitClient.save(new Fruit("Apple", "Keeps the doctor away"));
assertEquals(HttpStatus.CREATED, status);
fruits = fruitClient.findAll();
assertTrue(fruits.stream()
.filter(f -> f.getDescription() != null && f.getDescription().equals("Keeps the doctor away"))
.findFirst()
.isPresent());
embeddedServer.close();
MongoDbUtils.closeMongoDb();
}
}
@@ -0,0 +1,52 @@
package example.micronaut;

import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpRequest;
import static io.micronaut.http.HttpStatus.BAD_REQUEST;

import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@Property(name = "spec.name", value = "FruitControllerValidationTest")
@MicronautTest
public class FruitControllerValidationTest {
@Inject
@Client("/")
HttpClient httpClient;

@Test
public void testFruitIsValidated() {
HttpClientResponseException e = assertThrows(HttpClientResponseException.class, () ->
httpClient.toBlocking().exchange(HttpRequest.POST("/fruits",new Fruit("", "Hola"))));
assertEquals(HttpStatus.BAD_REQUEST, e.getStatus());
}

@Requires(property = "spec.name", value = "FruitControllerValidationTest")
@Singleton
@Replaces(FruitRepository.class)
static class MockFruitRepository implements FruitRepository {
@Override
public List<Fruit> list() {
return Collections.emptyList();
}

@Override
public void save(Fruit fruit) {

}
}
}
@@ -0,0 +1,31 @@
package example.micronaut;

import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.utility.DockerImageName;

public class MongoDbUtils {
static MongoDBContainer mongoDBContainer;

public static void startMongoDb() {
if (mongoDBContainer == null) {
mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.withExposedPorts(27017);
}
if (!mongoDBContainer.isRunning()) {
mongoDBContainer.start();
}
}

public static String getMongoDbUri() {
if (mongoDBContainer == null || !mongoDBContainer.isRunning()) {
startMongoDb();
}
return mongoDBContainer.getReplicaSetUrl();
}

public static void closeMongoDb() {
if (mongoDBContainer != null) {
mongoDBContainer.close();
}
}
}
18 changes: 18 additions & 0 deletions guides/micronaut-mongodb-synchronous/metadata.json
@@ -0,0 +1,18 @@
{
"asciidoctor": "micronaut-mongodb-synchronous.adoc",
"slug": "micronaut-mongodb-synchronous",
"title": "Micronaut MongoDB Synchronous",
"intro": "Learn how to use a blocking MongoClient with a Micronaut application",
"authors": ["Sergio del Amo"],
"tags": ["mongodb"],
"category": "Data Access",
"publicationDate": "2021-09-15",
"languages": ["java"],
"apps": [
{
"name": "default",
"features": ["mongo-sync"]
}
]
}