himanago

Azure・C#などのMS系技術やLINE関連技術など、好きな技術について書くブログ

Java 系 Web アプリの移行先としての Azure Static Web Apps + Micronaut for Azure Functions その 1

はじめに

Java 系 の Web アプリケーションを Azure で動かしたいと考えたとき、Web Apps や Azure Spring Cloud を使ってそのまま動かすのもありですが、最近 GA した Azure Static Web Apps を使ってよりサーバーレスにもっていけたりしないかな?と考えてみています。

Static Web Apps は Angular / React / Vue などで作ったシングルページアプリケーションのデプロイに最適なサービスで、静的サイトのホスティング + Azure Functions によるバックエンド API のセットで構成されています。

しかしこのバックエンド API、通常は Static Web Apps 専用の Functions が裏に用意されるのですが、現状 JS / C# / Python しかサポートされていません。

docs.microsoft.com

しかし、GA された Static Web Apps には、有料のスタンダードプランで使うことのできる「Bring your own functions」機能があり、これなら別途用意した Azure Functions をバックエンド API としてつなぐことができるため、Java の Azure Functions を使うことができます。

docs.microsoft.com

これを使って別途 Functions で作成した Java ベースなバックエンドを Static Web App につないでしまおうという寸法ですが、既存の Java のバックエンドのコードをどれだけ Functions に流用できるかが勝負です。

この流用をしやすくするために、Micronaut というマイクロサービスフレームワークが適しているのではないか?と考えています。

ちょうど業務のほうでも、以前から Azure 上で Grails 1 の Web アプリケーションを運用していて、もろもろのコストを考えてもう少しサーバーレスなかたちに移行したいと考えていたので、それを想定しつつ Java 系 Web アプリの移行先としての Azure Static Web Apps + Micronaut for Azure Functions を考えてみたいと思います。

なお、フロントエンドは Vue.js を使っていきます(これしかまともに書けない…)。

Micronaut とは

JVM ベースのマイクロサービス開発向けフレームワークです。

前にも少し紹介させていただきましたが、Azure Functions を公式にサポートしていて、Micronaut フレームワークを使って Function App の開発ができます。

himanago.hatenablog.com

Micronaut の利点としては、

  • Java/Kotlin/Groovy といった JVM 言語のメジャーどころが選べる
  • DI サポート
  • (DI を使っても)起動が高速

というのが挙げられます。

今回は Grails からの移行ということで、同じ Groovy 言語が使える、DI があるので移行元で使っていたサービスクラスをそのまま流用できるなどの利点が見込めそうです(これがこの構成をとるいちばんの理由)。

ローカルでの環境構築・開発

ということで、ローカルに環境を作って動かすところまでやってみます。

今回は Mac 想定で進めるので、Windows だと多少コマンドなど異なるかもしれません。そのあたりはご注意ください。

今回の検証環境:

  • OS: macOS Catalina 10.15.7
  • JDK: 8.0.292-zulu
  • Micronaut: 2.5.7

開発環境構築

まずは以下をいれておきます。

VS Code拡張機能

エディターは VS Code を使います。

以下の拡張機能を入れておきます。

SDKMAN!

Java や Groovy、Micronaut などをインストール・アップデートできる SDK マネージャーです。

Home - SDKMAN! the Software Development Kit Manager

未インストールであれば

curl -s "https://get.sdkman.io" | bash

でインストールし、インストール済みであれば以下で更新しておきます。

sdk update

JDK

Java 8 を使う必要があるので、JDK 8 を入れておきます。

sdk install java 8.0.292-zulu
sdk default  java 8.0.292-zulu

java -version して入れた JDK が出てくるようになっていれば OK です。

SDKMAN ではなくインストーラー経由で別バージョンの JDK を入れている場合はなおすのがちょっと面倒かもしれませんので、JDK は SDKMAN で管理するように切り替えておくとよいでしょう。

Micronaut

続いて Micronaut の本体です。

sdk install micronaut

vue-cli

Vue.js 開発用。Vue を使わない場合は当然不要です。

npm install -g @vue/cli

Static Web Apps CLI

ローカルで Static Web Apps を動かしたりできる CLI ツールです。

npm install -g @azure/static-web-apps-cli

とりあえず、使うツールは以上です。

フロントエンド(Vue.js)プロジェクトの作成

適当なフォルダに、Vue.js のアプリをつくります。ターミナルから

vue create static-web-app-micronaut-sample

を実行します。

Manually select features で、今回はこんなかんじにしておきます(このあたりはなんでもよいはず)。

? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

できあがったら、 cd して code を開きます。

cd static-web-app-micronaut-sample/
code .

ここから先は VS Code で作業です。

バックエンド(Micronaut)プロジェクトの作成

フロントエンドの Vue のプロジェクトを VS Code で開いたところですが、ここでバックエンドのプロジェクトを作成します。

フロントエンドとバックエンドは同一リポジトリにあげる monorepo で運用すると楽なので、Vue のプロジェクトのフォルダ内に Micronaut のプロジェクトを作っていきます。

VS Code のターミナルから、以下のコマンドを実行します。

mn create-function-app --build=gradle --jdk=8 --lang=groovy --test=spock --features=azure-function com.example.swa.api

パッケージ名とかは適当ですが、最後の api という部分が生成されるフォルダ名になるので、バックエンド・API あることがわかりやすい名前にしておくとよいでしょう。

ほぼ Java の簡易版なコード & Grails からの移行を見越している、ということでここでは言語を Groovy にしていますが、もちろん Java でも Kotlin でも大丈夫です。

ちなみに、上記のコマンドはこちらのサイトで生成可能です。

micronaut.io

使いたい機能を選択すると、オプションつきのコマンド文字列が得られます。ひながたを ZIP で落とすことも可能。

では、少し実装をいじっていきます。

GreetingService.groovy

DI を試したいので、簡単なサービスクラスを作っておきます。

package com.example.swa.services

import javax.inject.Singleton

@Singleton
class GreetingService {
    String greet(String name) {
        "Hello, ${name}!"
    }
}

Function.groovy

ひながたにある Function.groovy をそのまま使って、DI によるサービスクラスの利用をやってみます。

package com.example.swa

import com.microsoft.azure.functions.HttpStatus
import com.microsoft.azure.functions.ExecutionContext
import com.microsoft.azure.functions.HttpRequestMessage
import com.microsoft.azure.functions.HttpMethod
import com.microsoft.azure.functions.HttpResponseMessage
import com.microsoft.azure.functions.annotation.AuthorizationLevel
import com.microsoft.azure.functions.annotation.FunctionName
import com.microsoft.azure.functions.annotation.HttpTrigger
import com.example.swa.services.GreetingService
import io.micronaut.azure.function.AzureFunction
import java.util.Optional
import javax.inject.Inject

public class Function extends AzureFunction {
    @Inject
    GreetingService service

    @FunctionName("HttpTrigger")
    public HttpResponseMessage hello(
            @HttpTrigger(
                    name = "req",
                    methods = [HttpMethod.GET],
                    authLevel = AuthorizationLevel.ANONYMOUS)
                    HttpRequestMessage<Optional<String>> request,
            ExecutionContext context) {

            String message = service.greet("sample")

            request.createResponseBuilder(HttpStatus.OK).body(message).build()
    }
}

クラスの先頭で GreetingService を DI しているだけのシンプルな処理で、これを HttpTrigger という名前の関数にしています。

HelloWorld.vue

Vue.js で最初から作られている src/components/HelloWorld.vue に以下のような処理を追記します。

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>from api: {{ apiMessage }}</p>
  <!-- 中略 -->
</template>

<script>
import axios from 'axios'

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data: () => {
    return {
      apiMessage: ''
    }
  },
  created: function() {
    axios.get('/api/HttpTrigger')
      .then(response => {
        console.log(response.data);
        this.apiMessage = response.data;
      }).catch(err => {
        console.log(err.response);
        this.apiMessage = err.response.statusText;
      });
  }
}
</script>
<!-- 以下略 -->

axios を使って /api/HttpTrigger を叩いて、返ってきた内容を画面に表示させています。

npm install axios しておきましょう。

Static Web Apps のいいところのひとつが、こうやってフロントエンドと同一のルートからのパスで API が叩けるところですね。

ローカルでの実行

ここまできたら実行してみましょう。

コマンドから、まずはフロントエンドとバックエンドを VS Code でそれぞれ別のターミナルから立ち上げます。

フロントエンド(Vue.js)

npm run serve

バックエンド(Micronaut)

ターミナルをもうひとつ開き、以下のコマンドを実行します。

cd api
./gradlew clean azureFunctionsRun

Static Web App としての実行

SWA CLI の機能で、同一のルートにフロントエンドもバックエンドも配置されている状況を再現できます。

さらにターミナルを立ち上げて、以下のコマンドを実行します。

swa start http://localhost:8080/ --api http://localhost:7071

するとこのように、Static Web Apps 同様、同一の URL にフロントエンドもバックエンドもあるような状態をローカルでエミュレートしてくれます。

f:id:himanago:20210705010139p:plain

表示された URL にアクセスすると、このように表示されます。

f:id:himanago:20210705010434p:plain

ちゃんと Micronaut の API も呼んでくれているのがわかりますね。

まとめ

Micronaut 自体がおもしろいフレームワークですが、Azure Static Web Apps との組み合わせもなかなか強力そうな予感がしています!

今回はとりあえずローカル実行するところまで書いてみましたが、長くなりそうなので続きは別記事にしていきたいと思います。

このあとは、

  • Azure AD の適用
  • Azure へのデプロイ
  • CI/CD まわり

あたりを検証していこうかなと思っています。


  1. GrailsJVM 言語の Groovy ベースの Web アプリケーションフレームワークで、いってしまえば Java 系です。