MicronautでServerless Function

# Micronaut Severless Function
Micronautには、Serverless Functionを作成するための機能があります。

[Serverless Functions](https://docs.micronaut.io/1.0.4/guide/index.html#serverlessFunctions)

Springなどとの他のフレームワークに比べての利点は、Micronautはコンパイル時に依存関係を解決する仕組みや、メモリのフットプリントの軽さが売りのようです。

作成したFunctionは、AWS LambdaやOpenFaaSなどにデプロイすることができるようです。

[Deploying Functions](https://docs.micronaut.io/1.0.4/guide/index.html#deployingFunctions)

このMicronautのServerless Functionを作成する機能を、簡単に試してみたいと思います。

# 環境
環境は、こちらです。

```shell
$ mn -V
| Micronaut Version: 1.0.4
| JVM Version: 1.8.0_191
```

# プロジェクト作成

`--profile`を`function-aws`として、プロジェクトを作成します。

```shell
$ mn create-app --profile function-aws --build maven hello-function
$ cd hello-function
```

指定できる`function`のProfileは、`function-aws`のみのようなので…。

```shell
$ mn list-profiles
| Available Profiles
--------------------
  cli           The cli profile
  federation    The federation profile
  function-aws  The function profile for AWS Lambda
  kafka         The Kafka messaging profile
  profile       A profile for creating new Micronaut profiles
  service       The service profile
```

単に`function`とか書いたりすると、「そんなProfileはない」と怒られます。

```shell
$ mn create-app --profile function --build maven hello-function
| Error Profile [function] is designed only for extension and not direct usage
```

できあがったプロジェクトには、テストコードなども含まれています。

```shell
$ find -type f
./mvnw.cmd
./.mvn/jvm.config
./.mvn/wrapper/maven-wrapper.properties
./.mvn/wrapper/MavenWrapperDownloader.java
./.mvn/wrapper/maven-wrapper.jar
./.gitignore
./mvnw
./pom.xml
./micronaut-cli.yml
./src/main/java/hello/function/HelloFunctionFunction.java
./src/main/java/hello/function/Application.java
./src/main/resources/application.yml
./src/main/resources/logback.xml
./src/test/java/hello/function/HelloFunctionFunctionTest.java
./src/test/java/hello/function/HelloFunctionClient.java
```

スケルトンのfunctionは、こんな感じです。

src/main/java/hello/function/HelloFunctionFunction.java

```java
package hello.function;

import io.micronaut.function.FunctionBean;
import java.util.function.Supplier;

@FunctionBean("hello-function")
public class HelloFunctionFunction implements Supplier<String> {

    @Override
    public String get() {
        return "hello-function";
    }
}
```

functionを定義する方法は、`FunctionBean`として作成する方法と、`Factory`を使って作成する方法があるようです。

[FunctionBean](https://docs.micronaut.io/1.0.4/guide/index.html#functionBean)

今回は、`FunctionBean`を使った方法で実装されているというわけですね。

この場合、関数インターフェースを実装する必要があるようですが、利用できるインターフェースは`Supplier`、`Consumer`、`BiConsumer`、`Function`、`BiFunction`のようです。

`main`メソッドを持ったクラスは、こういう感じで。

src/main/java/hello/function/Application.java

```java
package hello.function;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}
```

実行するfunctionは、設定ファイルで指定されています。これは、`FunctionBean`の名前ですね。

src/main/resources/application.yml

```yaml
micronaut:
    function:
        name: hello-function
```

ドキュメントを見ると、複数のfunctionを含む場合は明示的に設定する必要があるみたいです。

# CLIとして実行してみる

パッケージングして実行してみましょう。

[Functions as CLI Applications](https://docs.micronaut.io/1.0.4/guide/index.html#functionCLI)

結果が返ってきました。

```shell
$ java -jar target/hello-function-0.1.jar 
12:56:07.817 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [function]
hello-function
```

# 引数を受け取ったり、他のBeanをDIしたりしてみる

ちょっと構成を変えて、引数を受け取ったり他のBeanをDIしたりしてみましょう。

引数のメッセージを加工するだけの、Beanを作成します。

src/main/java/hello/function/MessageService.java

```java
package hello.function;

import java.time.LocalDateTime;
import javax.inject.Singleton;

@Singleton
public class MessageService {
    public String wrap(String message) {
        return String.format("[%s] ★%s★", LocalDateTime.now(), message);
    }
}
```

もとのfunctionは、このBeanをDIしつつ、実行時に引数を受け取るようにしてみましょう。関数インターフェースは、`Function`を実装しました。

src/main/java/hello/function/HelloFunctionFunction.java

```java
package hello.function;

import io.micronaut.function.FunctionBean;

import java.util.function.Function;
import java.util.function.Supplier;
import javax.inject.Inject;

@FunctionBean("hello-function")
public class HelloFunctionFunction implements Function<HelloFunctionFunction.Request, HelloFunctionFunction.Response> {
    @Inject
    MessageService messageService;

    @Override
    public Response apply(Request request) {
        String replyMessage = messageService.wrap(request.getMessage());

        return new Response(replyMessage);
    }

    public static class Request {
        String message;

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }
    }

    public static class Response {
        String message;

        public Response(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }
    }
}
```

コードが変わってしまったので、今回はテストをスキップしてパッケージング。

```shell
$ ./mvnw package -DskipTests=true
```

実行。

```shell
$ echo '{"message": "こんにちは、世界"}' | java -jar target/hello-function-0.1.jar 
13:00:23.558 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [function]
{"message":"[2019-03-05T13:00:24.185] ★こんにちは、世界★"}
```

引数を受け取りつつ、他のBeanをDIして実行できました。

functionをWebアプリケーションとしても実行できるようですが、今回はパスします。

実行環境へのデプロイは、そのうち…。