Oracle Cloud Streaming and the Micronaut Framework - Event-Driven Applications at Scale
Use Oracle Cloud Streaming to communicate between your Micronaut applications.
On this guide
In this section
Getting Started
In this guide, we will create two Micronaut microservices written in Groovy that will use Oracle Cloud Streaming to communicate with each other in an asynchronous and decoupled way.
What You Will Need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 17 or greater installed with
JAVA_HOMEconfigured appropriately -
An Oracle Cloud account (create a free trial account at signup.oraclecloud.com)
-
Oracle Cloud CLI installed with local access to Oracle Cloud configured by running
oci setup config -
Docker and Docker Compose installed if you will be running Kafka in Docker and for running tests.
Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
Writing the Application
The microservices will use Oracle Cloud Streaming and the Kafka API to send and receive messages. The messages will represent chess gameplay events, including game start and end and each move.
The microservices are:
-
chess-game- Has a simple JavaScript and Ajax UI that renders a variable number of chess games that will be auto-played to generate many events -
chess-listener- Receives the chess event messages and persists them to a database
chess-game Microservice
Create the chess-game microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app --features=kafka,graalvm,reactor,testcontainers example.micronaut.chess-game --build=maven --lang=groovy|
Note
|
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the kafka, graalvm, reactor, and testcontainers features.
The previous command creates a directory named chess-game and a Micronaut application inside it with default package example.micronaut.
In addition to the dependencies added by the testcontainers feature, we also need a test dependency for Kafka in Testcontainers, along with one for the Awaitility library:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-kafka</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>@awaitilityVersion@</version>
<scope>test</scope>
</dependency>DTOs
Create an enum for the chess players:
package example.micronaut.chess.dto
import com.fasterxml.jackson.annotation.JsonValue
enum Player {
WHITE('w'),
BLACK('b');
private final String color
Player(String color) {
this.color = color
}
@JsonValue
String toString() {
color
}
}Create a GameDTO data-transfer object to represent game data:
Create a GameStateDTO data-transfer object to represent game move data:
GameReporter
Create a GameReporter Kafka client to send chess-related messages:
GameController
Create a GameController class to contain Ajax endpoints for the front end:
Development environment
Modify the Application class to use dev as a default environment:
The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.runtime.Micronaut
import static io.micronaut.context.env.Environment.DEVELOPMENT
@CompileStatic
class Application {
static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application)
.defaultEnvironments(DEVELOPMENT)
.start()
}
}Delete the kafka.bootstrap.servers config option from application.properties and move it to application-dev.properties.
Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.
Static Resources
Update application.properties to add static resource configuration:
UI Resources
Create index.html with the simple chess game UI:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Micronaut Chess Multi</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="favicon-32x32.png">
<link rel="stylesheet"
href="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css"
integrity="sha384-q94+BZtLrkL1/ohfjR8c6L+A6qzNH9R2hBLwyoAfu3i/WCvQjzL2RQJ3uNHDISdU"
crossorigin="anonymous">
<style>
.gamesRow {
width: 100%;
margin: 0 auto;
}
.gameContainer {
display: inline-block;
}
</style>
</head>
<body>
<div id="counts">
<div>
<label for="rowCount">Rows: </label><input type="number" value="1" min="1" max="10" id="rowCount">
</div>
<div>
<label for="gamesPerRow">Games per row: </label><input type="number" value="1" min="1" max="10" id="gamesPerRow">
</div>
<div>
<label for="playDelay">Play delay milliseconds: </label><input type="number" value="5" min="1" id="playDelay">
</div>
<div>
<button id="startButton">START</button>
</div>
</div>
<div id="gamesContainer"></div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"
integrity="sha384-8Vi8VHwn3vjQ9eUHUxex3JSN/NFqUg3QbPyX8kWyb93+8AC/pPWTzj+nHtbC5bxD"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.12.0/chess.min.js"
integrity="sha512-ujGsB4vTyNcSuViwM2DJ0+G2BIViQJl2rVBZBekStznA9Hq96+Wd9+jwu9zlttp0U2/9CYhgR7pOt2j+E6yewg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="micronaut-chess.js"></script>
</body>
</html>The HTML page includes the chessboard.js JavaScript library to create a chess board and the chess.js JavaScript library for chess game logic.
Create micronaut-chess.js used by index.html with the JavaScript code:
function updateStatus(n, started) {
if (started) {
onMove(n);
}
const game = GAMES[n];
if (game.in_checkmate()) {
onCheckmate(n);
}
else if (game.in_draw()) {
onDraw(n);
}
}
function onGameStart(n) {
$.post('/game/start', { b: BLACK_NAMES[n], w: WHITE_NAMES[n]}, function (data) {
const gameId = data;
GAME_IDS[n] = gameId;
$('#gameId' + n).text('Game ID: ' + gameId);
window.setTimeout(function () {
makeRandomMove(n);
}, 2000); // delay a bit so the Game is persisted
});
}
function onMove(n) {
const game = GAMES[n];
const history = game.history();
const move = history[history.length - 1];
$.post('/game/move/' + GAME_IDS[n], {
player: other(n),
fen: game.fen(),
pgn: game.pgn(),
move: move
});
}
function onCheckmate(n) {
const winner = other(n);
$.post('/game/checkmate/' + GAME_IDS[n] + '/' + winner);
}
function onDraw(n) {
$.post('/game/draw/' + GAME_IDS[n]);
}
function other(n) {
return GAMES[n].turn() === 'b' ? 'w' : 'b';
}
function makeRandomMove(n) {
const game = GAMES[n];
if (game.game_over()) {
restart(n);
return;
}
const possibleMoves = game.moves();
const moveIndex = Math.floor(Math.random() * possibleMoves.length);
game.move(possibleMoves[moveIndex]);
BOARDS[n].position(game.fen());
updateStatus(n, true);
window.setTimeout(function () {
makeRandomMove(n);
}, playDelay);
}
function restart(n) {
BOARDS[n].position(FEN_INITIAL);
GAMES[n].load(FEN_INITIAL);
updateStatus(n, false);
onGameStart(n);
}
function startGames() {
$('#counts').toggle();
playDelay = parseInt($('#playDelay').val(), 10);
const rowCount = parseInt($('#rowCount').val(), 10);
const gamesPerRow = parseInt($('#gamesPerRow').val(), 10);
const hWidth = (window.innerWidth - 50) / gamesPerRow;
const vWidth = window.innerHeight / rowCount - 50;
const gameWidth = Math.min(400, hWidth, vWidth);
for (let r = 0; r < rowCount; r++) {
const gamesContainer = $('#gamesContainer');
gamesContainer.append(
'<div id="gamesRow' + r + '" class="gamesRow"></div>'
);
for (let c = 0; c < gamesPerRow; c++) {
const n = r * gamesPerRow + c;
const gamesRow = $('#gamesRow' + r);
gamesRow.append(
'<div class="gameContainer" style="width: ' + gameWidth + 'px">' +
'<div id="chessboard' + n + '"></div>' +
'<div><span id="gameId' + n + '"></span></div>' +
'</div>'
);
GAMES[n] = new Chess();
BLACK_NAMES[n] = 'b' + n;
WHITE_NAMES[n] = 'w' + n;
BOARDS[n] = Chessboard('chessboard' + n, {
position: 'start',
appearSpeed: 0,
moveSpeed: 0
});
updateStatus(n, false);
onGameStart(n);
}
}
}
const FEN_INITIAL = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
const GAME_IDS = [];
const BOARDS = [];
const BLACK_NAMES = [];
const WHITE_NAMES = [];
const GAMES = [];
let playDelay = 5;
$('#startButton').on('click', startGames);Copy these chess piece images to src/main/resources/public/img/chesspieces/wikipedia (the path must be correct because it is hard-coded in chessboard.js):


Right-click each image and save to your local file system, or extract the completed example zip file linked above and get them from there.
chess-listener Microservice
Create the chess-listener microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app --features=kafka,graalvm,data-jdbc,flyway,reactor,testcontainers example.micronaut.chess-listener --build=maven --lang=groovy|
Note
|
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the kafka, graalvm, data-jdbc, flyway, reactor, and testcontainers features.
The previous command creates a directory named chess-listener and a Micronaut application inside it with default package example.micronaut.
In addition to the dependencies added by the testcontainers feature, we also need a test dependency for Kafka and Oracle in Testcontainers, along with one for the Awaitility library:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-kafka</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-oracle-xe</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>@awaitilityVersion@</version>
<scope>test</scope>
</dependency>Flyway
Enable Flyway database migrations for all environments by adding this configuration to application.properties:
flyway.datasources.default.enabled=trueDTOs
The same data transfer objects (GameDTO and GameStateDTO…) as above in the chess-game microservice. In a real application, these would be in a shared library, but to keep things simple, we’ll just duplicate them.
Entity Classes
Create a Game entity to represent persistent game data:
package example.micronaut.chess.entity
import example.micronaut.chess.dto.Player
import example.micronaut.chess.dto.GameDTO
import groovy.transform.CompileStatic
import io.micronaut.core.annotation.NonNull
import io.micronaut.core.annotation.Nullable
import jakarta.validation.constraints.NotBlank
import io.micronaut.data.annotation.DateCreated
import io.micronaut.data.annotation.DateUpdated
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import java.time.LocalDateTime
@MappedEntity('GAME')
@CompileStatic
class Game {
@Id
@NotNull
final UUID id
@Size(max = 255)
@NotBlank
@NonNull
final String blackName
@Size(max = 255)
@NotBlank
@NonNull
final String whiteName
@DateCreated
LocalDateTime dateCreated
@DateUpdated
LocalDateTime dateUpdated
boolean draw
@Nullable
@Size(max = 1)
Player winner
Game(@NonNull UUID id,
@NonNull String blackName,
@NonNull String whiteName) {
this.id = id
this.blackName = blackName
this.whiteName = whiteName
}
GameDTO toDto() {
new GameDTO(id.toString(), blackName, whiteName, draw, winner)
}
}Create a GameState entity to represent persistent game move data:
package example.micronaut.chess.entity
import example.micronaut.chess.dto.GameStateDTO
import groovy.transform.CompileStatic
import io.micronaut.core.annotation.NonNull
import io.micronaut.data.annotation.DateCreated
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.data.annotation.Relation
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
import example.micronaut.chess.dto.Player
import java.time.LocalDateTime
import static io.micronaut.data.annotation.Relation.Kind.MANY_TO_ONE
@MappedEntity('GAME_STATE')
@CompileStatic
class GameState {
@Id
@NotNull
@NonNull
final UUID id
@Relation(MANY_TO_ONE)
@NotNull
@NonNull
final Game game
@DateCreated
LocalDateTime dateCreated
@Size(max = 1)
@NotBlank
@NonNull
final Player player
@Size(max = 100)
@NotBlank
@NonNull
final String fen
@NotBlank
@NonNull
final String pgn
@Size(max = 10)
@NotBlank
@NonNull
final String move
/**
* @param id the id
* @param game the game
* @param player b or w
* @param move the current move
* @param fen https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
* @param pgn https://en.wikipedia.org/wiki/Portable_Game_Notation
*/
GameState(@NonNull UUID id,
@NonNull Game game,
@NonNull Player player,
@NonNull String move,
@NonNull String fen,
@NonNull String pgn) {
this.id = id;
this.game = game
this.player = player
this.move = move
this.fen = fen
this.pgn = pgn
}
@NonNull
GameStateDTO toDto() {
new GameStateDTO(id.toString(), game.getId().toString(), player, move, fen, pgn)
}
}Repositories
Create a "base" GameRepository interface to have access to methods for Game entity persistence:
package example.micronaut.chess.repository
import example.micronaut.chess.entity.Game
import io.micronaut.data.repository.CrudRepository
interface GameRepository extends CrudRepository<Game, UUID> {
}and a H2GameRepository interface that extends GameRepository and specifies the H2 dialect to use an in-memory H2 database in the development environment (we’ll also be creating an Oracle repository):
Create a "base" GameStateRepository interface to have access to methods for GameState entity persistence:
Also create a H2GameStateRepository interface that extends GameStateRepository:
package example.micronaut.chess.repository
import io.micronaut.context.annotation.Primary
import io.micronaut.context.annotation.Requires
import io.micronaut.data.jdbc.annotation.JdbcRepository
import static io.micronaut.context.env.Environment.DEVELOPMENT
import static io.micronaut.data.model.query.builder.sql.Dialect.H2
@Primary
@JdbcRepository(dialect = H2)
@Requires(env = DEVELOPMENT)
interface H2GameStateRepository extends GameStateRepository {
}GameService
Create GameService to coordinate transactional persistence using GameRepository and GameStateRepository:
package example.micronaut.chess
import example.micronaut.chess.dto.GameDTO
import example.micronaut.chess.dto.GameStateDTO
import example.micronaut.chess.entity.Game
import example.micronaut.chess.entity.GameState
import example.micronaut.chess.repository.GameRepository
import example.micronaut.chess.repository.GameStateRepository
import groovy.transform.CompileStatic
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import io.micronaut.core.annotation.NonNull
import jakarta.inject.Singleton
import jakarta.transaction.Transactional
@Singleton
@Transactional
@CompileStatic
class GameService {
private final Logger log = LoggerFactory.getLogger(GameService.name)
private final GameRepository gameRepository
private final GameStateRepository gameStateRepository
GameService(GameRepository gameRepository,
GameStateRepository gameStateRepository) {
this.gameRepository = gameRepository
this.gameStateRepository = gameStateRepository
}
/**
* Create a new <code>Game</code> and persist it.
*
* @param gameDTO the <code>Game</code> data
* @return the game
*/
Game newGame(GameDTO gameDTO) {
log.debug('New game {}, black: {}, white: {}',
gameDTO.id, gameDTO.blackName, gameDTO.whiteName)
Game game = new Game(UUID.fromString(gameDTO.id), gameDTO.blackName, gameDTO.whiteName)
gameRepository.save game
}
/**
* Persist a game move as a <code>GameState</code>.
*
* @param gameStateDTO the <code>GameState</code> data
*/
void newGameState(GameStateDTO gameStateDTO) {
Game game = findGame(gameStateDTO.gameId)
GameState gameState = new GameState(
UUID.fromString(gameStateDTO.id), game,
gameStateDTO.player, gameStateDTO.move,
gameStateDTO.fen, gameStateDTO.pgn)
gameStateRepository.save gameState
}
/**
* Record that a game ended in checkmate.
*
* @param gameDTO the <code>Game</code> data
*/
void checkmate(GameDTO gameDTO) {
log.debug('Game {} ended with winner: {}',
gameDTO.id, gameDTO.winner)
Game game = findGame(gameDTO.id)
game.winner = gameDTO.winner
gameRepository.update game
}
/**
* Record that a game ended in a draw.
*
* @param gameDTO the <code>Game</code> data
*/
void draw(GameDTO gameDTO) {
log.debug('Game {} ended in a draw', gameDTO.id)
Game game = findGame(gameDTO.id)
game.draw = true
gameRepository.update game
}
@NonNull
private Game findGame(String gameId) {
gameRepository.findById(UUID.fromString(gameId)).orElseThrow(() ->
new IllegalArgumentException("Game with id '" + gameId + "' not found"))
}
}ChessListener
Create ChessListener Kafka listener to receive messages sent from the chess-game microservice:
Development environment
Modify the Application class to use dev as a default environment:
The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.runtime.Micronaut
import static io.micronaut.context.env.Environment.DEVELOPMENT
@CompileStatic
class Application {
static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application)
.defaultEnvironments(DEVELOPMENT)
.start()
}
}Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.
H2 Flyway Migration Script
Create a database migration script to create the database tables:
CREATE TABLE game (
id CHAR(36) PRIMARY KEY,
black_name VARCHAR(255) NOT NULL,
white_name VARCHAR(255) NOT NULL,
date_created TIMESTAMP NOT NULL,
date_updated TIMESTAMP NOT NULL,
draw BOOLEAN NOT NULL,
winner CHAR(1)
);
CREATE TABLE game_state (
id CHAR(36) PRIMARY KEY,
game_id CHAR(36) NOT NULL,
date_created TIMESTAMP NOT NULL,
player CHAR(1) NOT NULL,
fen VARCHAR(100) NOT NULL,
pgn CLOB NOT NULL,
move VARCHAR(10) NOT NULL,
CONSTRAINT fk_game_state_game FOREIGN KEY (game_id) REFERENCES game(id)
);Kafka
We’ll use Oracle Cloud Streaming in the "real" application, but for local development, we can use a local Kafka instance.
Install Kafka
A fast way to start using Kafka is via Docker. Create this docker-compose.yml file:
Start ZooKeeper and Kafka (use CTRL-C to stop both):
docker-compose upAlternatively you can install and run a local Kafka instance.
Running the application
Start the chess-game microservice:
./mvnw mn:run16:35:55.614 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 576ms. Server Running: http://localhost:8080Start the chess-listener microservice:
./mvnw mn:run16:35:55.614 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 623ms. Server Running: http://localhost:8081Test the app functionality by opening http://localhost:8080/ in a browser. The UI lets you choose one or more chess games that will auto-play with the specified delay between plays. Events (game start and end, player moves) are sent to the server via Ajax and then sent to the chess-listener microservice for processing, analysis, etc.
You can, for example, start a single game with a moderately large delay between plays:
A single board is displayed:
Or you can start multiple games with a short delay (or any combination you want):
Multiple simultaneous boards are displayed:
Moving to Oracle Cloud
Oracle Autonomous Database (ATP)
Update the chess-listener microservice to support Oracle in addition to the in-memory H2 database.
Dependencies
Add the micronaut-oraclecloud-atp dependency to the chess-listener microservice to support using ATP:
<dependency>
<groupId>io.micronaut.oraclecloud</groupId>
<artifactId>micronaut-oraclecloud-atp</artifactId>
<scope>compile</scope>
</dependency>Configuration
Create src/main/resources/application-oraclecloud.properties. The Micronaut framework applies this configuration file only for the oraclecloud environment.
Repositories
Create the OracleGameRepository interface that extends GameRepository and specifies the ORACLE dialect in the oraclecloud environment:
Create the OracleGameStateRepository interface that extends GameStateRepository:
package example.micronaut.chess.repository
import io.micronaut.context.annotation.Primary
import io.micronaut.context.annotation.Requires
import io.micronaut.data.jdbc.annotation.JdbcRepository
import static io.micronaut.context.env.Environment.ORACLE_CLOUD
import static io.micronaut.context.env.Environment.TEST
import static io.micronaut.data.model.query.builder.sql.Dialect.ORACLE
@Primary
@JdbcRepository(dialect = ORACLE)
@Requires(env = [ORACLE_CLOUD, TEST])
interface OracleGameStateRepository extends GameStateRepository {
}Flyway
Create a database migration script to create the Oracle database tables:
CREATE TABLE game (
id CHAR(36) PRIMARY KEY,
black_name VARCHAR2(255) NOT NULL,
white_name VARCHAR2(255) NOT NULL,
date_created TIMESTAMP NOT NULL,
date_updated TIMESTAMP NOT NULL,
draw NUMBER(3) NOT NULL,
winner CHAR(1)
);
CREATE TABLE game_state (
id CHAR(36) PRIMARY KEY,
game_id CHAR(36) NOT NULL,
date_created TIMESTAMP NOT NULL,
player CHAR(1) NOT NULL,
fen VARCHAR2(100) NOT NULL,
pgn CLOB NOT NULL,
move VARCHAR2(10) NOT NULL,
CONSTRAINT fk_game_state_game FOREIGN KEY (game_id) REFERENCES game(id)
);Oracle Cloud Streaming
Up to now, we’ve been using a local Kafka, but let’s configure the equivalent infrastructure in OCI. This will involve minimal application changes thanks to the ability to send and receive Cloud Streaming messages using Kafka APIs, and Micronaut support for Kafka.
Stream Pool and Streams
Log in to your Oracle Cloud tenancy and from the Oracle Cloud Menu, select "Analytics & AI" and then "Streaming":
Choose the compartment to create the streams in, then click "Create Stream Pool":
Enter a name for the pool, e.g., "mn-guide-pool", and click "Create":
Click the "Copy" link in the OCID row and save the value for later. Also save the "FQDN" URL. Click "Create Stream":
Create two streams within the pool you created with the Topic names used in the microservices. First create "chessGame":
and then create "chessGameState":
User and Group
Create a group for the streams by clicking the Oracle Cloud menu and selecting "Identity & Security" and then click "Groups":
Click "Create Group":
Choose a name and a description, e.g., "mn-guide-streaming-group", and click "Create":
Create a user by clicking the Oracle Cloud menu and selecting "Identity & Security" and then click "Users":
Click "Create User":
Choose a name and a description, e.g., "mn-guide-streaming-user", and click "Create":
Scroll down and click "Add User to Group":
Select the group you created and click "Add":
You’ll need an auth token to use as the password in the Micronaut Kafka configuration. Click "Auth Tokens" and then "Generate Token":
Enter a name for the token, e.g., "mn-guide-streaming", and click "Generate Token":
Copy the token to the clipboard and save it for later:
See the Groups and Users docs for more information.
Policy
Create a policy to grant various Streams access to the user and group you created.
Open the Oracle Cloud Menu and click "Identity & Security" and then "Policies":
Select the compartment where you created the streams from the dropdown and click "Create Policy":
Choose a name and description, e.g., "mn-guide-streaming-policy", and click "Show Manual Editor". Copy the following and paste it into the "Policy Builder" field, replacing "micronaut-guides" with the name of the compartment you’re using, and click "Create":
Application configuration
Create src/main/resources/application-oraclecloud.properties in the chess-game microservice. Add the following there, and also add it to the application-oraclecloud.properties you already created in the chess-listener microservice:
Local Testing with Cloud Resources
You can now start both microservices in the oraclecloud environment to use Cloud Streaming and the ATP database you created:
To run each application use:
MICRONAUT_ENVIRONMENTS=oraclecloud ./mvnw mn:runor if you use Windows:
cmd /C "set MICRONAUT_ENVIRONMENTS=oraclecloud && mvnw mn:run"Writing Tests
We’ll run Kafka inside a Docker container using Testcontainers for both application tests and also run Oracle database inside a Docker container for testing persistence in the chess-listener tests.
chess-game tests
Create a test in the chess-game microservice to verify that Kafka message processing works:
chess-listener tests
Create a test in the chess-listener microservice to verify that Kafka message processing and database persistence works:
Create application-test.properties file in src/test/resources with this content:
datasources.default.url=jdbc:tc:oracle:thin:@/xe
datasources.default.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
datasources.default.username=system
datasources.default.password=oracle
flyway.datasources.default.locations=classpath:db/migration/oracle
flyway.datasources.default.baseline-version=0
flyway.datasources.default.baseline-on-migrate=trueRunning the tests
To run the tests:
./mvnw testDeploy to OCI
Once you’ve verified that the microservices work with the configured cloud resources, you can deploy the microservices to Compute instances and run everything in Oracle Cloud.
Instance Principal authentication
The current configuration in application-oraclecloud.properties works when running locally using OCI resources (ATP database and Cloud Streams) but won’t work when deploying the application because it doesn’t make sense to install the Oracle Cloud CLI in Compute instances. Instead, we’ll use Instance Principal authentication.
To use this, we need to update the config, create a dynamic group, and add policy statements granting permissions.
Dynamic Group
Create a Dynamic Group by clicking the Oracle Cloud menu and selecting "Identity & Security" and then click "Dynamic Groups":
Click "Create Dynamic Group":
Then enter a name and description for the group, e.g., "mn-streaming-guide-dg", and a matching rule, i.e., the logic that will be used to determine group membership. We’ll make the rule fairly broad - enter ALL {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaxxxxx'} replacing ocid1.compartment.oc1..aaaaaxxxxx with the compartment OCID where you’re creating your Compute instances and click "Create":
See the Dynamic Group docs for more information.
Dynamic Group Policy Statements
Edit the policy you created earlier and add three new policies: one to grant access to Autonomous Database, one to allow sending stream messages, and one to allow receiving stream messages:
Configuration
Edit application-oraclecloud.properties in the chess-listener microservice and replace
oci:
config:
profile: DEFAULTwith
oci:
config:
instance-principal:
enabled: trueNext Steps
Read more about Kafka support in the Micronaut framework.
License
|
Note
|
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images). |