Fork me on GitHub

CosmosDB Tutorial

This tutorial demonstrates using CosmosDB to read/write data from Azure CosmosDB.

The example used in this tutorial is three end points:

  • POST /posts {"message":"Message to post"} to create a post
  • GET /posts/{id} to obtain a particular post
  • GET /posts to obtain all posts

Tutorial Source

WoOF configuration

The configuration of the end points are as follows:

CosmosDbHttpServer screen shot.

With the implementation as follows:

public class CosmosDbLogic {

	public void savePost(Post post, CosmosEntities entities, ObjectResponse<Post> response) {
		CosmosContainer container = entities.getContainer(Post.class);
		Post created = container.createItem(new Post(UUID.randomUUID().toString(), post.getMessage())).getItem();
		response.send(created);
	}

	public void retrievePost(@HttpPathParameter("id") String identifier, CosmosEntities entities,
			ObjectResponse<Post> response) {
		CosmosContainer container = entities.getContainer(Post.class);
		PartitionKey partitionKey = entities.createPartitionKey(new Post());
		Post post = container.readItem(identifier, partitionKey, Post.class).getItem();
		response.send(post);
	}

	public void retrieveAllPosts(CosmosEntities entities, ObjectResponse<Post[]> response) {
		CosmosContainer container = entities.getContainer(Post.class);
		PartitionKey partitionKey = entities.createPartitionKey(new Post());
		Post[] posts = container.readAllItems(partitionKey, Post.class).stream().toArray(Post[]::new);
		response.send(posts);
	}
}

The CosmosDB entity is as follows:

@CosmosEntity(containerId = "POST")
@HttpObject
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Post {

	private String id;

	private String message;

	@CosmosPartitionKey
	public String getPartitionKey() {
		return "same";
	}
}

The Cosmos annotations are optional:

  • @CosmosEntity allows specifying a container name. If not provided, then the class name is used for the container name.
  • @CosmosPartitionKey flags a particular attribute as the partition key. If not provided, the id is used.

Configuring CosmosDB

The following dependency is required:

		<dependency>
			<groupId>net.officefloor.persistence</groupId>
			<artifactId>officenosql_cosmosdb</artifactId>
		</dependency>
		
		<!-- Required for testing -->
		<dependency>
			<groupId>net.officefloor.persistence</groupId>
			<artifactId>officenosql_cosmosdb_test</artifactId>
			<scope>test</scope>
		</dependency>

CosmosDB is configured in application.objects as follows:

<objects>
	
	<supplier source="net.officefloor.nosql.cosmosdb.CosmosDbSupplierSource">
		<property name="cosmos.entity.locators" value="net.officefloor.tutorial.cosmosdbhttpserver.CosmosDbEntities" />		
	</supplier>
	
</objects>

For performance reasons, the entities are not dynamically discovered. As Azure starts instances as required, the application must be brought up quickly to service the first request. Having to inspect all jars and classes for entities is typically too slow. Therefore, CosmosDB requires registering all entities with it manually.

Therefore, to make the entities available to CosmosDB, the following is the above configured class:

public class CosmosDbEntities implements CosmosEntityLocator {

	@Override
	public Class<?>[] locateEntities() throws Exception {
		return new Class[] { Post.class };
	}

}

Note: for third party libraries requiring to store data, it is also possible to register entities via a CosmosEntityLocatorServiceFactory. This allows the entities to be automatically registered when the library is added to the class path.

Testing

To make local testing easier, the following unit tests demonstrate automatically setting up a local data store for testing.

	@Order(1)
	@RegisterExtension
	public final CosmosDbExtension cosmosDb = new CosmosDbExtension().waitForCosmosDb();

	@Order(2)
	@RegisterExtension
	public final MockWoofServerExtension server = new MockWoofServerExtension();

	private @Dependency CosmosEntities entities;

	@Test
	public void ensureCreatePost() throws Exception {

		// Have server create the post
		Post post = new Post(null, "TEST");
		MockWoofResponse response = this.server.send(MockWoofServer.mockJsonRequest(HttpMethod.POST, "/posts", post));
		response.assertStatus(200);

		// Ensure post created
		PartitionKey partitionKey = this.entities.createPartitionKey(new Post());
		Post[] created = this.entities.getContainer(Post.class).readAllItems(partitionKey, Post.class).stream()
				.toArray(Post[]::new);
		assertEquals(1, created.length, "Should only be one created post");
		assertEquals("TEST", created[0].getMessage(), "Incorrect post");
	}

Note that this will start @zeit/cosmosdb-server within a docker container to emulate CosmosDB. Unfortunately, the Azure provided emulator only runs on windows.

JUnit 4 example:

	private final CosmosDbRule dynamoDb = new CosmosDbRule().waitForCosmosDb();

	private final MockWoofServerRule server = new MockWoofServerRule(this);

	@Rule
	public final RuleChain ordered = RuleChain.outerRule(this.dynamoDb).around(this.server);

	private @Dependency CosmosEntities entities;

	@Test
	public void ensureCreatePost() throws Exception {

		// Have server create the post
		Post post = new Post(UUID.randomUUID().toString(), "TEST");
		MockWoofResponse response = this.server.send(MockWoofServer.mockJsonRequest(HttpMethod.POST, "/posts", post));
		response.assertStatus(200);

		// Ensure post created
		CosmosContainer container = this.entities.getContainer(Post.class);
		PartitionKey partitionKey = this.entities.createPartitionKey(post);
		Post[] created = container.readAllItems(partitionKey, Post.class).stream().toArray(Post[]::new);
		assertEquals("Should only be one created post", 1, created.length);
		assertEquals("Incorrect post", "TEST", created[0].getMessage());
	}

Next

The next tutorial covers the CosmosDB asynchronous client.