Developing Microservices in Java, JavaScript, Python, .NET, and Go with the Oracle Converged Database

This is the first in a series of blogs on data-driven microservices design mechanisms and transaction patterns with the Oracle converged database. The goal of this first blog is to illustrate how to connect to an Oracle database in Java, JavaScript, Python, .NET, and Go as succinctly as possible with source and Dockerfile examples making it a quick and easy way for you to get your microservices to connect whether on-prem or in the cloud and take advantage of the Oracle converged database.  JavaScript, Python, and Go drivers are built on Oracle's C stack "Oracle Call Interface" API and the Oracle Data Provider for .NET (ODP.NET) Core is used as the .NET driver.

The complete source can be found at https://github.com/oracle/microservices-datadriven and you can take the "Building Microservices with Oracle Converged Database Workshop" found at http://bit.ly/simplifymicroservices at anytime to easily set up a full micro services environment complete with an OCI Kubernetes cluster, 2 ATP (Autonomous Transaction Processing) Oracle databases, AQ messaging propagation, container registry, object storage, etc. in ~25 minutes!

In future pieces of this series we will explore different SQL calls (queries, commands, store procedure calls, etc.) and data models (JSON, Spatial, XML, etc.). We will also show messaging with Oracle AQ, convenience features in various frameworks, and delve into details of data-driven micro service patterns.  All shown in these five languages, but for now let’s just get connected…

We'll provide just the basic facts you need... imports, source, and Dockerfile.  There can of course be a few variations in how connections can be obtained (wallet location, use of connection pool, how connection strings are constructed, etc.) but what is presented in the following are the general approaches for connectivity and can be modified as needed. 

Instructions for downloading the client credentials (wallet) needed to connect to an Oracle cloud database such as ATP can be found here and the TNS_ADMIN environment variable must be assigned to the location of this unzipped wallet. Generally the wallet is mounted in a Kubernetes deployment such that it can be set in the environment of the microservice container(s) it deploys. You can refer to the various *-deployment.yaml files in the repos and workshop for examples of this.

Likewise, you will notice the connection property values in the source snippets are taken from the environment. The DB_CONNECT_STRING is the service name of the db (as found in the tnsnames.ora file) for all languages except Java where it is the full JDBC connection URL.  Note that in Java it is also possible to set the TNS_ADMIN value via this URL whereas all other languages require it be set as an environment value as previously mentioned.  See snippets for example comments.  

Java

source...

import oracle.ucp.jdbc.PoolDataSource;
import java.sql.Connection;
import java.sql.SQLException;

PoolDataSource dataSource = PoolDataSourceFactory.getPoolDataSource();
dataSource.setUser(System.getEnv("DB_USER"));
dataSource.setPassword(System.getEnv("DB_PASSWORD"));
dataSource.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
dataSource.setURL(System.getEnv("url")); //for example "jdbc:oracle:thin:@examplepdb_tp?TNS_ADMIN=/msdataworkshop/creds"
Connection connection = dataSource.getConnection();
Dockerfile...

FROM openjdk:11-jre-slim

//Oracle OJDBC and UCP jars are packaged with application jar via Maven dependencies
ENTRYPOINT ["java", "-jar", "/usr/share/myservice/myservice.jar"]
ADD target/libs /usr/share/myservice/libs
ARG JAR_FILE
ADD target/${JAR_FILE} /usr/share/myservice/myservice.jar

Python

source...

import cx_Oracle

db_user = env.get('DB_USER')
db_password = env.get('DB_PASSWORD')
db_connect_string = env.get('DB_CONNECT_STRING') //for example "examplepdb_tp"
pool = cx_Oracle.SessionPool(
db_user,
db_password,
db_connect_string)
conn = pool.acquire()
Dockerfile...

FROM oraclelinux:7-slim

ARG release=19
ARG update=9
RUN yum -y install oracle-release-el7 && \
yum-config-manager --enable ol7_oracle_instantclient && \
yum -y install oracle-instantclient${release}.${update}-basiclite && \
yum install -y oracle-epel-release-el7
WORKDIR /app
COPY inventory/requirements.txt .
RUN yum install -y python36 && \
yum install -y tar && \
rm -rf /var/cache/yum && \
python3.6 -m pip install -r requirements.txt
ADD myapp .
CMD ["gunicorn", "app:app", "--config=config.py"]

JavaScript

source...

const oracledb = require('oracledb');

const dbConfig = {
inventoryPool: {
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
connectString: process.env.DB_CONNECT_STRING //for example "examplepdb_tp"
}
};
const pool = await oracledb.createPool(dbConfig.inventoryPool);
connection = await oracledb.getConnection();
Dockerfile...

FROM oraclelinux:7-slim

ARG release=19
ARG update=9
RUN yum -y install oracle-release-el7 && \
yum-config-manager --enable ol7_oracle_instantclient && \
yum -y install oracle-instantclient${release}.${update}-basiclite && \
yum install -y oracle-epel-release-el7
RUN yum -y install oracle-nodejs-release-el7 && \
yum-config-manager --disable ol7_developer_EPEL && \
yum -y install nodejs && \
rm -rf /var/cache/yum
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node", "app.js" ]

.NET

source...

using System.Data;
using System.Data.Common;
using Oracle.ManagedDataAccess.Client;

OracleConfiguration.WalletLocation = Environment.GetEnvironmentVariable("TNS_ADMIN");
string connString =
"User Id=" +
Environment.GetEnvironmentVariable("DB_USER") +
";Password=" +
Environment.GetEnvironmentVariable("DB_PASSWORD") +
";Data Source=" +
Environment.GetEnvironmentVariable("DB_CONNECT_STRING") + //for example "examplepdb_tp"
";";
OracleConnection connection = new OracleConnection(connString)
Dockerfile...

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY /app /app
ENTRYPOINT ["dotnet", "inventory-dotnet.dll"]
Dockerfile (including build)...

FROM mcr.microsoft.com/dotnet/sdk:5.0.300-alpine3.13-amd64 AS build
WORKDIR /src
COPY inventory-dotnet.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "inventory-dotnet.dll"]

Go

source...

import (
"context"
"database/sql"
"github.com/godror/godror"
)

user := os.Getenv("DB_USER")
dbpassword := os.Getenv("DB_PASSWORD")
connectString := os.Getenv("DB_CONNECT_STRING") //for example "examplepdb_tp"
connectionString := user + "/" + dbpassword + "@" + connectString
connection, err := sql.Open("godror", connectionString)
Dockerfile...

FROM alpine:latest
ENV LD_LIBRARY_PATH=/lib
RUN wget https://download.oracle.com/otn_software/linux/instantclient/193000/instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
unzip instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
cp -r instantclient_19_3/* /lib && \
rm -rf instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
apk add libaio && \
apk add libaio libnsl libc6-compat
RUN cd /lib
RUN ln -s /lib64/* /lib
RUN ln -s libnsl.so.2 /usr/lib/libnsl.so.1
RUN ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2
COPY /go/bin/inventory-go /usr/lib/inventory-go
ENTRYPOINT ["/usr/lib/inventory-go"]
Dockerfile (including build)...

FROM golang:alpine AS builder
RUN apk update && apk add --no-cache git build-base
WORKDIR /src
COPY . .
RUN go get -d -v
RUN go build -o /go/bin/inventory-go

FROM alpine:latest
ENV LD_LIBRARY_PATH=/lib
RUN wget https://download.oracle.com/otn_software/linux/instantclient/193000/instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
unzip instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
cp -r instantclient_19_3/* /lib && \
rm -rf instantclient-basic-linux.x64-19.3.0.0.0dbru.zip && \
apk add libaio && \
apk add libaio libnsl libc6-compat
RUN cd /lib
RUN ln -s /lib64/* /lib
RUN ln -s libnsl.so.2 /usr/lib/libnsl.so.1
RUN ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2
COPY --from=builder /go/bin/inventory-go /usr/lib/inventory-go
ENTRYPOINT ["/usr/lib/inventory-go"]

Conclusion

As mentioned, this is just the beginning in a series of blogs that will go into various aspects of polyglot microservices using the Oracle converged database not limited to the following...

Various SQL calls (queries, updates, store procedure calls, etc.)

Various data models such as JSON, Spatial, XML, and so on

Messaging with AQ and Kafka APIs

Various frameworks (such as Helidon, Micronaut, Springboot, .NET, DJango, Express, etc.)

Data-driven microservice patterns such as Event Sourcing, CQRS, Sagas, etc.

Please feel free to provide any feedback here, on the workshop, on the github repos, or directly. We are happy to hear from you.

I would like to thank Kuassi Mensah, Alex Keh, Christian Shay, Christopher Jones, Richard Exley, Irina Granat, and Curtis Dunkel for their development help in these languages and contributions to the workshop.

32