メッセージング基盤NATSを試す

# NATSとは
イベント駆動アーキテクチャの基盤となる、メッセージングミドルウェアにあたるソフトウェアです。

https://docs.nats.io/nats-concepts/what-is-nats

[現在CNCFでは](https://www.cncf.io/blog/2018/03/15/cncf-to-host-nats/)`incubating`プロジェクトとなっています。
また、[thoughtworks社のTechnology Radar](https://www.thoughtworks.com/radar/platforms/nats)でも、volume24時点で`Assess`となっています。

## メッセージングの保証
`At most once`（最大で一回）と`At-least/exactly once`（最低もしくは確実に一回）があります。
https://docs.nats.io/nats-concepts/what-is-nats#nats-quality-of-service-qos

NATSを使い始める場合、まずは[Core NATS](https://docs.nats.io/nats-concepts/core-nats)という基本のサーバから導入しますが、
それだけだとAt most onceになり、
加えて[JetStream](https://docs.nats.io/nats-concepts/jetstream)というシステムを使うと、外部ストレージへの永続化が可能になり、
At-least/exactly onceが可能になります。

## 他プロダクトとの比較
競合にあたるソフトウェアとしては、KafkaやRabbit MQなどが該当します。
[プロダクトの比較表](https://docs.nats.io/nats-concepts/overview/compare-nats)
CNCFプロジェクトということもあってか、Prometheus exporterやGrafana dashboardなどがサポートされてますね。

また、これらのソフトウェアに共通することですが、メッセージングを登録する側(Publisher/Producer)と、消費する側(Subscriber/Consumer)を、独立したコンポーネントとして設計することで、互いの状態に依存せずに非同期の仕組みにでき、各コンポーネントを疎結合にすることができます。

# 実践
公式にあるDocker imageで実践します

https://docs.nats.io/running-a-nats-service/nats_docker

## Docker composeでクラスタを構築する

ポート4222がクライアントになるクラスタリング構成です。
ポート6222がクラスターのルーティングポートで、
各サーバはリクエストを`--cluster`で指定し、`--routes`に接続情報を記述します。

https://docs.nats.io/running-a-nats-service/introduction/flags#cluster-options


```yaml:nats-cluster.yaml
version: "3.5"
services:
  nats:
    image: nats
    ports:
      - "8222:8222"
    command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --http_port 8222 "
    networks: ["nats"]
  nats-1:
    image: nats
    command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222"
    networks: ["nats"]
    depends_on: ["nats"]
  nats-2:
    image: nats
    command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222"
    networks: ["nats"]
    depends_on: ["nats"]

networks:
  nats:
    name: nats
```

インスタンスを作成します。

```
$ docker compose -f nats-cluster.yaml up
[+] Running 3/0
 ⠿ Container nats-nats-1    Created                                                                                                                                                                      0.0s
 ⠿ Container nats-nats-1-1  Created                                                                                                                                                                      0.0s
 ⠿ Container nats-nats-2-1  Created                                                                                                                                                                      0.0s
Attaching to nats-nats-1, nats-nats-1-1, nats-nats-2-1
nats-nats-1    | [1] 2022/01/23 15:03:27.133998 [INF] Starting nats-server
nats-nats-1    | [1] 2022/01/23 15:03:27.134083 [INF]   Version:  2.7.0
nats-nats-1    | [1] 2022/01/23 15:03:27.134097 [INF]   Git:      [not set]
nats-nats-1    | [1] 2022/01/23 15:03:27.134121 [INF]   Name:     NDLRORYKRIS7AFKF2P3FTNR32ZVQDZ7JKM375AE6Z64UO6DJ7HZ7AZJJ
nats-nats-1    | [1] 2022/01/23 15:03:27.134140 [INF]   ID:       NDLRORYKRIS7AFKF2P3FTNR32ZVQDZ7JKM375AE6Z64UO6DJ7HZ7AZJJ
nats-nats-1    | [1] 2022/01/23 15:03:27.135757 [INF] Starting http monitor on 0.0.0.0:8222
nats-nats-1    | [1] 2022/01/23 15:03:27.135919 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-1    | [1] 2022/01/23 15:03:27.136514 [INF] Server is ready
nats-nats-1    | [1] 2022/01/23 15:03:27.136572 [INF] Cluster name is NATS
nats-nats-1    | [1] 2022/01/23 15:03:27.136879 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-2-1  | [1] 2022/01/23 15:03:28.014862 [INF] Starting nats-server
nats-nats-2-1  | [1] 2022/01/23 15:03:28.014934 [INF]   Version:  2.7.0
nats-nats-2-1  | [1] 2022/01/23 15:03:28.014946 [INF]   Git:      [not set]
nats-nats-2-1  | [1] 2022/01/23 15:03:28.015124 [INF]   Name:     NA764AGYL3FA7VZEBN6B6MYMQB3XL3FAGACLCYPILTZBJDNLIJAAQ47A
nats-nats-2-1  | [1] 2022/01/23 15:03:28.015147 [INF]   ID:       NA764AGYL3FA7VZEBN6B6MYMQB3XL3FAGACLCYPILTZBJDNLIJAAQ47A
nats-nats-2-1  | [1] 2022/01/23 15:03:28.016274 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-2-1  | [1] 2022/01/23 15:03:28.016522 [INF] Server is ready
nats-nats-2-1  | [1] 2022/01/23 15:03:28.016577 [INF] Cluster name is NATS
nats-nats-2-1  | [1] 2022/01/23 15:03:28.016626 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-2-1  | [1] 2022/01/23 15:03:28.023049 [INF] 172.18.0.2:6222 - rid:4 - Route connection created
nats-nats-1    | [1] 2022/01/23 15:03:28.022990 [INF] 172.18.0.3:56610 - rid:4 - Route connection created
nats-nats-1-1  | [1] 2022/01/23 15:03:28.087152 [INF] Starting nats-server
nats-nats-1-1  | [1] 2022/01/23 15:03:28.087370 [INF]   Version:  2.7.0
nats-nats-1-1  | [1] 2022/01/23 15:03:28.087383 [INF]   Git:      [not set]
nats-nats-1-1  | [1] 2022/01/23 15:03:28.087432 [INF]   Name:     NADV6SOGHIG7MRQFH5K3K5NPOJIY7Q6G5WGQ2IRY4KJDXNFED6BUMXST
nats-nats-1-1  | [1] 2022/01/23 15:03:28.087508 [INF]   ID:       NADV6SOGHIG7MRQFH5K3K5NPOJIY7Q6G5WGQ2IRY4KJDXNFED6BUMXST
nats-nats-1-1  | [1] 2022/01/23 15:03:28.089795 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-1-1  | [1] 2022/01/23 15:03:28.090691 [INF] Server is ready
nats-nats-1-1  | [1] 2022/01/23 15:03:28.090762 [INF] Cluster name is NATS
nats-nats-1-1  | [1] 2022/01/23 15:03:28.090994 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-1    | [1] 2022/01/23 15:03:28.094351 [INF] 172.18.0.4:41872 - rid:5 - Route connection created
nats-nats-1-1  | [1] 2022/01/23 15:03:28.094383 [INF] 172.18.0.2:6222 - rid:4 - Route connection created
nats-nats-2-1  | [1] 2022/01/23 15:03:28.096072 [INF] 172.18.0.4:6222 - rid:5 - Route connection created
nats-nats-1-1  | [1] 2022/01/23 15:03:28.096196 [INF] 172.18.0.3:57406 - rid:5 - Route connection created
```

## Publish/Subscribe

pub/subクライアント用に、nats-boxというイメージを実行します。


```
$ docker run --network nats --rm -it synadia/nats-box
             _             _               
 _ __   __ _| |_ ___      | |__   _____  __
| '_ \ / _` | __/ __|_____| '_ \ / _ \ \/ /
| | | | (_| | |_\__ \_____| |_) | (_) >  < 
|_| |_|\__,_|\__|___/     |_.__/ \___/_/\_\
                                           
nats-box v0.5.0
```

NATSは通常のメッセージングであるようなtopicベースpub/subではなく、
Subject-Based Mappingという仕組みでデータマッピングします。

https://docs.nats.io/nats-concepts/subjects

以下のように`hello`でsubscribeしている場合に、

```
2028c118ec83:~# nats sub -s nats://nats:4222 hello &
2028c118ec83:~# 15:28:37 Subscribing on hello
```

`hello`でpublishすると、正常に受信できます。

```
2028c118ec83:~# nats pub -s "nats://nats-1:4222" hello first
15:28:46 Published 5 bytes to "hello"
[#1] Received on "hello"
first
```

そして`world`とpublishしたものは、上記では受信されません。

```
2028c118ec83:~# nats pub -s "nats://nats-1:4222" world second
15:28:54 Published 6 bytes to "world"
```

クラスタリングしている別ノードでも同様です。

```
2028c118ec83:~# nats pub -s "nats://nats-2:4222" hello third
15:29:03 Published 5 bytes to "hello"
[#2] Received on "hello"
third

2028c118ec83:~# nats pub -s "nats://nats-2:4222" world fourth
15:29:33 Published 6 bytes to "world"
2028c118ec83:~# 
```

# 最後に
最近はKafkaも[Amazon MSK](https://aws.amazon.com/jp/msk/)などあり、
zookeeperの管理も不要になるなど使いやすくなってきていますが、
NATSも軽量でレイテンシーも少なく非常に使いやすいと感じました。

KafkaはJavaのエコシステムが揃っている印象でしたが、
NATS自体がGoで作られていることもあり、Goのクライアントなどのサポートも充実しているようなので、
今後試してみたいと思っています。