[Microservices] Micronaut、GOAMとATPを使ってマイクロサービスを作成/Creating A Microservice With Micronaut, GORM And Oracle ATP

原文はこちら
https://blogs.oracle.com/developers/creating-a-microservice-with-micronaut-gorm-and-oracle-atp

ここ一年の間に、Micronautフレームワークは非常にポピュラーになっています。そしてそれは理由あってのものでしょう。このフレームワークはJVMワールドにとって革命的なもので、コンパイル時の依存性注入、およびReflectionを利用しないAOPを使っています。このことが意味するのは、起動および実行パフォーマンスと、メモリ消費に関して大きな良い効果があるということです。フレームワークはパフォーマンスが良いだけではなく、使いやすくドキュメントもしっかり整備されていなければなりませんが、Micronautはこの両方を満たしています。使っていて楽しく、Groovy、KotlinやGraalVMとの組み合わせで素晴らしい仕事をしてくれます。さらに、Micronautの中のひとたちは、業界が向かっている方向を理解しており、このフレームワークをその方向を意識して作りあげてきました。サーバレスやクラウドデプロイメントに容易にし、それらを助けるような機能を備えています。

このポストでは"Person" APIを公開するマイクロサービスをMicronautを使って作る方法を紹介していきます。このサービスでは、データベース(トラディショナルなRDBMSからMongoDB、Neo4Jなどなどまで)をとても容易に利用することができる「データアクセスツールキット」であるGORMを使用します。具体的に言うと、GORMをOracle Autonomous Transaction Processing DB(ATPデータベース)とやり取りするためのHibernateとして使用します。以下のポイントを見ていきます。
  1. Groovyを使ってMircronautアプリケーションを作成する
  2. そのアプリケーションがGORMを使ってATPデータベースと接続するように設定する
  3. Personモデルを作成する
  4. Personに対してCRUDオペレーションを行うPersonサービスを作成する
  5. Personサービスとやり取りするControlerを作成する
まずはじめに、起動、稼働中であるATPデータベースのインスタンスをお持ちであることを確認してください。幸運なことに、ATPデータベースの構築はとても簡単で、私のボスのGerald Venzlが5分もかからずにATPインスタンスを構築する方法 のポストで説明もしてくれています。稼働中のインスタンスが容易できたら、クライアント証明書ウォレットをコピーしてローカル端末のどこかにunzipしておいてください。
次のステップに進む前に、ATPインスタンスで新しいスキーマを作成し、そこに以下のDDLでひとつテーブルを作成します:
CREATE TABLE person (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
version NUMBER(20) NOT NULL,
first_name VARCHAR2(50) NOT NULL,
last_name VARCHAR2(50) NULL,
is_cool NUMBER(1,0) NOT NULL
);
これでMicronautアプリケーションを作成する次のステップの準備ができました。

Micronautアプリケーションの作成

これまでに使ったことがなければ、Micronautをインストールする必要があります。これにはアプリケーション自体やControllerなどの要素を扱いやすくするための便利なCLIも含まれています。インストールできていることを確認したら、以下のコマンドを実行してベーシックなアプリケーションを作成しましょう。
$ mn create-app codes.recursive.gorm.atp.demo --features=groovy
view rawgenerate-app.sh hosted with ❤ by GitHub
ディレクトリの中を一覧し、CLIによって生成されたものを見てみましょう。
見てお分かりのように、CLIによってGradleビルドスクリプト、Dockerfileおよびいくつかの設定ファイルと`src`ディレクトリが作成されています。このディレクトリは以下のようになっています:
この時点で、あなたのお気に入りのIDEにこのアプリケーションをインポートできるようになっているのでインポートしておきましょう。次のステップはCotrollerの生成です:
$ mn create-controller codes.recursive.gorm.atp.controller.Person
view rawcreate-controller.sh hosted with ❤ by GitHub
生成されたControllerに小さな修正を加えます。Controllerを開いて`@CompileStatic`アノテーションを加えましょう。修正イメージは以下です:
package codes.recursive.gorm.atp.controller
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.HttpStatus
@CompileStatic
@Controller("/person")
class PersonController {
@Get("/")
HttpStatus index() {
return HttpStatus.OK
}
}
では`gradle run`(ラッパー経由の/gradlew runでも)を使ってアプリケーションを実行しましょう。アプリケーションが起動すると、ブラウザやcURLコマンドで稼働していることを確認できます。アプリケーションが立ち上がると、コンソールに以下のようなメッセージが表示されます:
10:49:18.118 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 934ms. Server Running: http://localhost:8080
view rawmicronaut-started.sh hosted with ❤ by GitHub
テストしてみましょう:
何もコンテンツは返してきませんが、'200 OK'が見えるのでアプリケーションによってリクエストが受け付けられ、適切にレスポンスを返していることがわかります。
ローカルでのアプリケーションの開発とテストを簡単にするために、わたしは自分のIDE(IntelliJ IDEA)にカスタム実行/デバッグ設定を作成し、カスタムGradleタスクを参照させておきます。あとで結局システムプロパティを渡すことになりますが、これによりIDEから起動したときにそれをやれるようになります。以下のように`build.grade`に新しいタスク`myTask`を作成します:
task myRun(type: JavaExec){
classpath sourceSets.main.runtimeClasspath
main = mainClassName
systemProperties = System.properties
}
view rawbuild.gradle hosted with ❤ by GitHub
ではこのタスクを参照するカスタム実行/デバッグ設定を作成し、のちほどOracle DB接続に必要となるVMオプションを追加しておきましょう:
コピペ用に追加が必要なプロパティを置いときます:
-Doracle.net.tns_admin="/path/to/atp/wallet"
-Djavax.net.ssl.trustStore="/path/to/atp/wallet/truststore.jks"
-Djavax.net.ssl.trustStorePassword="[wallet truststore password]"
-Djavax.net.ssl.keyStore="/path/to/atp/wallet/keystore.jks"
-Djavax.net.ssl.keyStorePassword="[wallet keystore password]"
-Doracle.net.ssl_server_dn_match=true
-Doracle.net.ssl_version="1.2"
-DdataSource.username=user
-DdataSource.password=L33THax0r!!!!~@@~
view rawvm-options.txt hosted with ❤ by GitHub
次のステップに進んでアプリケーションがATPとおしゃべりできるようにしましょう!

アプリケーションのGORMとATP用の設定

アプリケーションを設定する前にOracle JDBCドライバがあることを確認しておきましょう。ダウンロードし、アプリケーションのルートに`libs`というディレクトリを作成して配置しましょう。以下のJARが`libs`ディレクトリにあることを確認してください:
`build.gradle`の`dependencies`ブロックを修正し、Oracle JDB JARと`micronaut-hibernate-gorm`アーティファクトを依存に含めておいてください:
dependencies {
compile 'io.micronaut.configuration:micronaut-hibernate-gorm'
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile "io.micronaut:micronaut-http-client"
compile "io.micronaut:micronaut-http-server-netty"
compile "io.micronaut:micronaut-runtime-groovy"
compile "io.micronaut:micronaut-validation"
compileOnly "io.micronaut:micronaut-inject-groovy"
runtime "ch.qos.logback:logback-classic:1.2.3"
testAnnotationProcessor "io.micronaut:micronaut-inject-java"
testCompile "org.junit.jupiter:junit-jupiter-api"
testCompile "io.micronaut.test:micronaut-test-junit5"
testCompile("org.spockframework:spock-core") {
exclude group: "org.codehaus.groovy", module: "groovy-all"
}
testCompile "io.micronaut:micronaut-inject-groovy"
testCompile "io.micronaut.test:micronaut-test-spock:1.0.1"
testRuntime "org.junit.jupiter:junit-jupiter-engine"
}
view rawbuild.gradle hosted with ❤ by GitHub
`src/main/resources/application.yml`にあるファイルを修正し、データソースとHibernateを設定しましょう:
micronaut:
application:
name: demo
dataSource:
pooled: true
dbCreate: validate
url: jdbc:oracle:thin:@barnevents_low?TNS_ADMIN=/path/to/atp/wallet
driverClassName: oracle.jdbc.driver.OracleDriver
hibernate:
dialect: org.hibernate.dialect.Oracle12cDialect
cache:
queries: false
use_second_level_cache: false
use_query_cache: false
region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory
view rawapplication.yml hosted with ❤ by GitHub
これでアプリケーションがGORM経由でATPとやり取りすることができるようになったので、サービス、モデルといくつかのControllerメソッドを作成していきましょう!まずはモデルから始めます。

モデルの作成

GORMモデルを使うのはすごく簡単です。それはモデルエンティティであることを示し、Bean Validation APIでバリデーションをさせるための特別なアノテーションが追加されただけのPOGO(Plain Old Groovy Object)です。ここから'Person.groovy'というGroovyクラスを`model`という新ディレクトリに作成することで、`Person`モデルオブジェクトを作成していきましょう。以下のようにモデルを作ってください:
package codes.recursive.gorm.atp.model
import grails.gorm.annotation.Entity
import groovy.transform.CompileStatic
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
@CompileStatic
@Entity
class Person {
@NotNull
@Size(min=5, max=50)
String firstName
@Size(min=5, max=50)
String lastName
@NotNull
Boolean isCool = false
}
view rawPerson.groovy hosted with ❤ by GitHub
ここでいくつかのアイテムに注目してみましょう。クラスに@Entity(`grails.gorm.annotation.Entity`)のアノテーションを付けたので、GORMはこのクラスを管理対象であると知ることができます。われわれのモデルは3つのプロパティ、firstName、lastNameとisCoolを持っています。`person`テーブルを作った際に使ったDDLを見返すと、モデルに結び付けられていないふたつのカラム、IDとversionがあることに気づくでしょう。IDカラムはGORMエンティティで暗黙に用いられ、versionカラムもエンティティの楽観的ロックを扱うためにGORMに自動的に管理されます。また、のちに見ていくことになるいくつかのデータバリデーションに用いるプロパティのアノテーションも登場します。
ここでもういちどアプリケーションを起動するとGORMがエンティティを特定してMicronautがHibernateの設定をしているのを見ることができます:
次はサービスの作成を行っていきます。

サービスの作成

わたしは嘘は言いませんよ。もしあなたがステップが難しくなっていることを待っているなら、そんなことにはならないと言っておきましょう。`Person` CRUDオペレーションを管理するためのサービスの作成は、本当に簡単です。`PersonService`というGroovyクラスを`service`という新ディレクトリに作成し、以下のように記述しましょう:
package codes.recursive.gorm.atp.service
import codes.recursive.gorm.atp.model.Person
import grails.gorm.services.Service
import groovy.transform.CompileStatic
import javax.validation.constraints.NotNull
@CompileStatic
@Service(Person)
abstract class PersonService {
abstract int count()
abstract List<Person> findAll()
abstract List<Person> findAll(Map args)
abstract Person find(@NotNull Long id)
abstract Person save(Person person)
abstract Person delete(@NotNull Long id)
}
view rawPersonService.groovy hosted with ❤ by GitHub
実際のところこれだけです。これでこのサービスはControllerから渡されたオペレーションを扱うことができます。GORMは十分にスマートなので、ここで渡したメソッドシグネチャを受け取ってメソッドを実装してくれます。abstractクラスアプローチ(interfaceアプローチではなく)を使うののいいところは、追加でメソッドの実装が必要となるビジネスロジックがあれば自分でメソッドを実装することができる点です。
まだ目に見えるような変更を加えてないので、ここでアプリケーションを再起動する必要はありません。次でControllerを修正することで変更が目に見えるようになります。

Controllerの作成

前のステップで作成した`PersonController`を修正して永続化オペレーションを行うために使うエンドポイントを追加しましょう。まず、PersonServiceをこのControllerにinjectする必要があります。これもまた簡潔でおこなうことができ、以下をクラス宣言部に追加するだけです。
@Inject PersonService personService
このControllerの中で最初にやっておくべきなのは`Person`の保存メソッドです。このための`@Post`とアノテーションをつけたメソッドを追加し、このメソッドの中で`PersonService.save()` メソッドを呼び出させます。うまくいった場合には新規に作成した`Person`を返却し、うまくいかなかった場合にはバリデーションエラーのリストを返却します。MicronautはHTTPリクエストのボディをControllerメソッドの`person`引数にバインドするので、このメソッドの中では完全に埋められた`Person`ビーンを取得できる点に留意してください。
@Post("/save")
HttpResponse<Map> savePerson(@Body Person person) {
try {
return HttpResponse.ok( [ person: personService.save(person) ] as Map )
}
catch(ValidationException e) {
return HttpResponse.unprocessableEntity().body(
[
person: person,
errors: e.errors.allErrors.collect {
FieldError err = it as FieldError
[
field: err.field,
rejectedValue: err.rejectedValue,
message: err.defaultMessage
]
}
]
) as HttpResponse<Map>
}
}
これでアプリケーションを起動すると、`/person/save/`エンドポイントを用いて`Person`を永続化できるようになりました:
200 OKレスポンスとともに`Person`を格納したオブジェクトが返却されていることに注目ください。しかし、不適格なデータを用いてオペレーションを試してみると、エラーが返却されることになります:
われわれのモデルでは`Person`firstNameは(とても妙な話ではありますが)5~50文字でなければならないと指定していたため、バリデーションエラーの配列を格納した422 Unprocessable Entityがレスポンスと返されてきます。
ここで、ATPインスタンスの中に格納されているすべてのPersonオブジェクトをクエリするために使う`/list`エンドポイントを追加していきましょう。ページネーションに使うためのふたつのオプショナルな引数も設定しておきます。
@Get("/list{/offset}{/max}")
List<Person> getPersons(@Nullable Optional<Integer> offset, @Nullable Optional<Integer> max) {
if( offset && max ) {
return personService.findAll([offset: offset.get(), max: max.get()])
}
else {
return personService.findAll()
}
}
Remember that our `PersonService` had two signatures for the `findAll` method - one that accepted no parameters and another that accepted a `Map`.  The Map signature can be used to pass additional parameters like those used for pagination.  So calling `/person/list` without any parameters will give us all `Person` objects:
`PersonService`には`findAll`メソッドとしてふたつのシグネチャを持っていることを思い出してください――ひとつはパラメータを受け付けておらず、もうひとつはMapを受け付けるものです。Mapシグネチャはページネーションに使うもののような追加の引数を渡すのに使えます。ということで、パラメータなしで`/person/list`を呼び出すと、全ての`Person`オブジェクトを返してくれます:
あるいはページネーションパラメータを使うとサブセットが取れます:
また、IDで`Person`を取得するための`/person/get`エンドポイントも追加しておきましょう:
@Get("/get/{id}")
Person getPerson(int id) {
return personService.find(id)
}
`Person`を削除するための`/person/delete`エンドポイントも追加しましょう:
@Delete("/delete/{id}")
HttpResponse<Map> deletePerson(Long id) {
try {
return HttpResponse.ok( [person: personService.delete(id), deleted: true] as Map )
}
catch(e) {
return HttpResponse.unprocessableEntity().body(
[
message: "Could not delete person with ID: ${id}"
]
) as HttpResponse<Map>
}
}

まとめ

Micronautがパフォーマンスの高いマイクロサービスアプリケーションを作成する際のシンプルでありながらパワフルなやり方であること、また、Hibernate/GORMを用いてのOracle ATPバックエンドへのデータ永続化が簡単に実現できることがおわかりいただけたかと思います。
ここで紹介したアプリケーションの全体を見たい場合はGithubで見るかクローンしてくださいね。

0 件のコメント:

コメントを投稿