見出し画像

AWS EventBridgeでマイクロサービス x サーバーレス x イベント駆動アーキテクチャ開発した話

はじめに

こんにちは、エアークローゼットでエンジニアをしているNghiaです。
この記事は、airCloset Advent Calendar 2021 22日目の記事です。
よろしくお願いします!

背景

エアークローゼットではairCloset, airCloset Fitting(pickss),airCloset Mallと複数事業展開しています。サービスごとに特化してシステムを開発していたために、事業の横展開に弱くて、同じ機能なのに、似て非なるものを開発しなければならない状態です。

そのため、ドメインを事業単位ではなく、事業の枠を超えた機能単位でマイクロサービスとして切り出し抽象化していくのが必要となります。

例えば、airClosetでもairCloset Fittingでもスタイリストがお客様の要望を元にスタイリングを行うことができるような「共通スタイリングサービス」を開発することです。

対応方針

マイクロサービス開発するために、色んな技術スタックがありますが、弊社ではインフラが殆どAWS使用しているので、マイクロサービスでもAWSで展開できる技術・ツールを考慮した上に、イベント駆動アーキテクチャ x AWS EventBridgeを選びました。

Amazon EventBridge は、独自のアプリケーション、AWSのサービスからのデータを使用して、アプリケーションを簡単に接続することを可能にするサーバーレスイベントバスです。EventBridgeはイベントを発行したり、発行されたイベントをフィルタリングし(イベント駆動)、所定のイベントが発生した場合のみLambda関数実行など特定の処理を実施することができます。

それに、サーバーレスアーキテクチャにすれば、今後の運用コストも削減できるメリットがあるので、楽かなと思っています。

開発環境

CDK

AWS Cloud Development Kit (AWS CDK) は、使い慣れたプログラミング言語(typescript, python, java)を使用してクラウドアプリケーションリソースを定義するためのオープンソースのソフトウェア開発フレームワークです。

CDK + CodePipelineを使うと、定義したリソースを簡単にAWSにデプロイできるし、cdk localstackを使っら、開発環境においてAWSのサービスを擬似的に使用できるので、ローカル開発&テストがとても楽です。

EventBridge

今回、複数サービスで同じ項目のデータを共通化にする仕組みでEventBridgeの使い方を説明させていただきます。
16日目に書いたredisのstreamsでデータ共通化にする話がありましたが、redisがイベント数を増加する時、メンテナンスが複雑になってしまうデメリットがあったので、EventBridgeに移行することになりました。

やりたいこと
ユーザー情報を管理するuser-managementサービスのデータをairclosetとfittingに同期することです。
以下のアーキテクチャで実現します。

EventBridgeアーキテクチャ

Event Bus
まずは「user-management-event-bus」というCustom Event Busを作成します。

import { EventBus } from '@aws-cdk/aws-events'export class UserManagement extends Construct {
  public readonly userManagementBus: EventBus;

  constructor(scope: Construct, id: string, props: UserManagementProps) {
    super(scope, id);

    const { logicalEnv: prefix } = props;

    this.userManagement = new EventBus(this, 'UserManagementBus', {
      eventBusName: `${prefix}-user-management-event-bus`
    });
  }
}

イベントバスとは、イベントを受信するパイプラインです。イベントバスに関連付けられたルールによって、受信したイベントが評価されます。各ルールは、イベントがルールの条件に一致するかどうかをチェックします。ルールを特定のイベントバスに関連付けると、そのルールはそのイベントバスで受信したイベントにのみ適用されます。

ルール
ユーザー情報は基本情報とスタイリング情報を分けるので、2ルールを作成します。
・user-basic-info-changed-ruleはユーザー基本情報(名前、年齢、住所、…..)のルール

   import { EventBus, Rule } from '@aws-cdk/aws-events'const BasicInfoChangedUserEventsRule = new Rule(this, 'BasicInfoChangedUserEventsRule', {
      ruleName: `${prefix}-basic-info-changed-user-events-rule`,
      description: 'Rule matching user_basic_info_changed events',
      eventBus: userManagementBus,
      eventPattern: {
        detailType: ['User Basic Info Changed']
      }
    });

・user-styling-info-changed-ruleはユーザースタイリング情報(サイズ、カルテ、ファッション趣味情報、….)のルール

const StylingInfoChangedUserEventsRule = new Rule(this, 'StylingInfoChangedUserEventsRule', {
      ruleName: `${prefix}-styling-info-changed-user-events-rule`,
      description: 'Rule matching user_styling_info_changed events',
      eventBus: userManagementBus,
      eventPattern: {
        detailType: ['User Styling Info Changed']
      }
    });

ルールは、受信したイベントをマッチングし、処理のためにターゲットに送信します。1 つのルールで複数のターゲットにイベントを送信し、並行して実行することができます。

ターゲットはstepfunctionからlambda実行することでAirclosetとFittingにデータ同期行います。

import * as targets from '@aws-cdk/aws-events-targets';
import { StateMachine, Parallel } from '@aws-cdk/aws-stepfunctions';
import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';

export class UserBasicInfoChangedStateMachineTarget extends Construct {
  public readonly stateMachine: StateMachine;

  constructor(scope: Construct, id: string, props: UserBasicInfoChangedStateMachineTargetProps) {
    super(scope, id);

    const { logicalEnv: prefix, callApp1ApiFn, callApp2ApiFn } = props;

    // state machine
    const callApp1ApiJob = new tasks.LambdaInvoke(this, 'CallApp1Api', {
      lambdaFunction: callApp1ApiFn,
      invocationType: tasks.LambdaInvocationType.EVENT
    });

    const callApp2ApiJob = new tasks.LambdaInvoke(this, 'CallApp2Api', {
      lambdaFunction: callApp2ApiFn,
      invocationType: tasks.LambdaInvocationType.EVENT
    });

    const definition = new Parallel(this, 'taskParallelTasks', {}).branch(callApp1ApiJob).branch(callApp2ApiJob);

    this.stateMachine = new StateMachine(this, 'LogUserBasicInfoChangedEvent', {
      definition,
      stateMachineName: `${prefix}-log-user-basic-info-changed-event`,
      tracingEnabled: true
    });
  }
}

// イベントルールのターゲットを設定 
  const stateMachineTarget = new UserBasicInfoChangedStateMachineTarget();

   BasicInfoChangedUserEventsRule.addTarget(
     new targets.SfnStateMachine(stateMachineTarget.stateMachine)
   );

   StylingInfoChangedUserEventsRule.addTarget(
     new targets.SfnStateMachine(stateMachineTarget.stateMachine)
   );

EventBridge schema registry
さらに、イベントの発行、フィルタリング、またイベントを受け取ったターゲット内の処理にてイベントのスキーマを理解している必要があります。

例えば間違ったスキーマのイベントを発行すると、受け取る側が正しくフィルタリングや処理ができません。そこで一元的にイベントスキーマを管理する場所として用意されたのがEventBridge schema registryです。各種イベントのスキーマ(≒構造、定義、形式)を格納する場所です。

また、コードバインディング機能があります。これはschema registryで閲覧できる各イベントスキーマのJava・Python・TypeScriptのコードバインディング をダウンロードできる機能です。これを利用すればより簡単にイベント駆動処理のコードが書けます。

EventBridge Schema Register

まとめ

システムの規模がどんどん大きくなって、メンテナンス生が低くなってしまう課題はマイクロサービスアーキテクチャに再構築しないと解決できないかなと思っています。EventBridgeでサーバーレスとしてマイクロサービス展開できるので、メンテナンスが便利になって、運用コストも削減できるじゃないかなと思います。

これから、共通のitem、order、stylingサービスもEventBridge経由で動かすように対応する予定です。

さいごに

ちなみに、私は元々アプリケーションエンジニアなので、あまりインフラを触ったことがない中で開発しました。

エアクロでは、エンジニアに多くの新しい経験をさせていただくのはとてもありがたいです。

皆様もご興味あればぜひぜひ!->採用サイト


この記事が気に入ったらサポートをしてみませんか?