Amazon Web Services ブログ

WEB 開発の未来: AWS Amplify のコードファーストアプローチ

AWS Amplify の新しいコードファースト開発者エクスペリエンスは、ウェブ開発の未来を形作ることに貢献しています。このアプローチでは、AWS サービスの力を活用しながら、シームレスなデベロッパーエクスペリエンス(DX)に焦点を当てて、アプリファーストの考え方で構築することに重点が置かれています。このアプローチを採用することで、開発者は顧客のニーズを満たす堅牢でスケーラブルなフルスタックアプリケーションを作成できます。このブログ記事では、開発者がアプリを開発、拡張、デリバリーする際に直面するストレスの原因と、Amplify がこれらの課題をどのように解決するかについて詳しく説明します。コード生成から拡張性の実現、デプロイの合理化まで、Amplify にはフルスタックのアプリケーションをクラウドで構築するために必要なものがすべて揃っています。

この記事では、AWS Amplify のコードファーストアプローチの背後にある要件と主な利点について説明します。

ストレスの原因

開発者が開発プロセスにおけるストレスを経験すると、新機能の提供、バグの解決、顧客の問題への対応などが遅れる可能性があります。これはひいてはカスタマーエクスペリエンス全体に影響します。デベロッパーエクスペリエンス(以下 DX)の低下は、通常、次の 3 つの領域のいずれかに分類されます。

開発

あなたのウェブ開発者としての時間は、ウェブアプリの機能構築に費やされます。この構築過程を自動化したり、コーディング体験を向上させるベンダーのツールやサービスは、生産性と効率を大幅に向上させることができます。しかし、これらが意識的に設計されていない場合、それらは貧弱なエクスペリエンスの一因となる可能性があります。

ここでは、開発者のソフトウェア構築を支援するツールが考慮すべき、優れた DX のための一般的な考慮事項を紹介します:

  • 冗長性の排除:CRUD API や UI コンポーネントのような冗長なコードを、ツールやサービスが生成する
  • シームレスなパフォーマンス:導入してしてすぐ(Out-of-the-box)に効果を得られるようにする
  • 型の安全性:TypeScript は新しい標準であり、支持されるに十分な理由がある

拡張

デフォルト設定は便利ですが、特定のアーキテクチャの決定を強制するものであり、将来、その決定が古くなる可能性があります。開発者のエクスペリエンスを向上させようとするあまり、ベンダーは自分のスタイルに合わないデフォルトに縛り付けてしまう可能性があります。

  • 拡張性のある API:API を拡張できるだけでなく、ベンダーはパズルのピースを公開し、そこにあなた自身のピースをはめ込めるようになっているべき
  • ロジックの持ち込み:ベンダーの API の一部を利用せず、代わりに独自のロジックを持ち込めるべきである

(原文を尊重し、表現を直訳しています)

デリバリー

ウェブ開発者として完全に自動化すべきことが 1 つあるとすれば、それはプロダクトデリバリーです。残念なことに、私たちの多くはインフラストラクチャの設定に関してスキルギャップを抱えていますが、それは問題ありません。なぜなら、あなたの責任は開発に集中しているからです。

  • 自動化: ホスティング機能だけでは不十分です。使い慣れた言語の CI/CD パイプラインを通じて、変更を加えて自動的にロールアウトできる必要があります
  • 親しみある言語でのインフラストラクチャ設定: インフラストラクチャの設定経験は、ウェブ開発者に合わせて調整されるべきです。JavaScript 開発者の場合は JS を使用し、YAML の記述方法を理解して知っている場合は YAML を使用してサーバーを構成できる必要があります

それぞれの DX に関する考慮事項についてより深いレベルで説明し、AWS Amplify がそれぞれをどのように解決するかについても見ていきましょう。

開発: Amplify の型安全性

TypeScript の人気は最近急上昇し、2023 年Stack Overflow Developer Surveyで 5 番目に愛されている言語になりました。TypeScript の人気は、ウェブ開発における柔軟性と堅牢性の両方を備えた言語を好むトレンドを反映しています。開発者は、エラーを早期に発見し、共同作業を合理化し、大規模なコードベースに明確な構造を提供する TypeScript の機能を高く評価しています。

型安全性により、実行時ではなく開発中にエラーを検出できるため、デバッグやテストにかかる時間を大幅に節約できます。また、コードを通じた明確なコミュニケーションが鍵となるコラボレーション環境では、コードの可読性と保守性が向上します。

ウェブ開発における TypeScript の人気と利点に加えて、TypeScript の Isomorphic 機能は急速に人気を集めています。TypeScript の Isomorphic 機能を使うと、開発者はサーバーコードとクライアントコードの両方からアクセスできる型を作成できます。つまり、開発者はサーバーとクライアント間でコードを共有できるため、コードの重複が減り、保守が容易になります。AWS Amplify は、これらすべての DX が組み込まれ、型安全性ファースト(タイプセーフファースト)になりました。

Amplify TS developer experience

開発: 自動化されたパフォーマンス

私たちが Nuxt.js や Next.js をデフォルトにしている究極の理由は、SSR や SSG などのためではありません。100/100 のパフォーマンススコアの結果として、素晴らしい体験をお客様に提供したいからです。私は、Next.js、Astro、Nuxt、Remix が何をするか以上にパフォーマンスを気にするよう提唱していますが、基礎を正しくすることを心配する必要がないのは、すがすがしいことです。

React と Vue はブラウザで実行できる JavaScript を生成し、あらゆるパフォーマンスと SEO コストが発生する中で簡単にデプロイ/提供できるようにします。しかし、クライアントでハイドレートする前に、まずサーバーでページをレンダリングする必要がある場合、SSR のビルドプロセスと出力を制御する必要が出てきます。ここで Amplify Hosting の出番です。SSR のデプロイを気にすることなく、SSR フレームワークを使用してパフォーマンスを重視した構築が可能になります。ほとんどの場合、必要なのはリポジトリを接続することだけです。

AWS Amplify は Next.js アプリをすぐにサポートし、Nuxt アプリもホスティングできるようになりました。

開発: コード生成

Web 開発での CRUD (作成、読み取り、更新、削除) の実装は反復的な作業で、多くの場合はこれらの開発にかなりの時間がかかります。この課題に対処するため、TypeScript 開発者はこれらのタスクを自動化してワークフローを合理化するコードジェネレーターに目を向けました。

コードジェネレーターは、データモデル、API クライアント、UI コンポーネント、フォーム UI を生成する場合に特に役立ちます。このような反復的なタスクを自動化することで、プロジェクトのより複雑でクリエイティブな側面に集中できます。さらに、コードジェネレーターは型の安全性を導入し、コード品質を向上させることで、DX を向上させることができます。

Amplify Gen 2 では、コードの記述時にロジックと型が自動的に生成され、CLI コマンドを明示的に実行する必要がなくなるため、自動化がさらに一歩進んでいます。Gen 2 は TypeScript ファーストで、フロントエンドコード用の「データクライアント」をデータスキーマから生成することができます。その結果、開発者はこのクライアントを利用して CRUDL (作成、読み取り、更新、削除、一覧表示) 処理を記述するだけでよくなり、CRUD のための関数を書くという、これまで行ってきた反復的な作業から解放されます。

以下のコードスニペットは、Gen 2 でスキーマを定義する方法を示しています。DefineData 関数を使用してデータのスキーマと認証モードを設定します。これは、Gen 2 がバックエンドのデータモデル定義を型の安全性で合理化する方法を示しています。

// Backend

import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
  Todo: a
    .model({
      content: a.string(),
      done: a.boolean(),
      priority: a.enum(["low", "medium", "high"]),
    })
    .authorization([a.allow.owner()]),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});

以下のコードスニペットは、aws-amplify/datagenerateClient 関数を利用して、バックエンドとやり取りするための型付きクライアントインスタンスを作成する React コンポーネントです。

// Frontend

import { useState, useEffect } from 'react';
import { generateClient } from 'aws-amplify/data';
import { Schema } from '@/../amplify/data/resource';


export default function HomePage() {

  const client = generateClient<Schema>();
  const [todos, setTodos] = useState<Schema['Todo'][]>([]);

  useEffect(() => {
    async function listTodos() {
        // fetch all todos
        const { data } = await client.models.Todo.list();
        setTodos(data);
    }
    listTodos();
  }, []);

  useEffect(() => {
    const sub = client.models.Todo.observeQuery()
      .subscribe(({ items }) => setTodos([...items]))

    return () => sub.unsubscribe()
  }, []);

  return (
    <main>
      <h1>Hello, Amplify 👋</h1>
      <button onClick={async () => {
        // create a new Todo with the following attributes
        const { errors, data: newTodo } = await client.models.Todo.create({
          content: "This is the todo content",
          done: false,
          priority: 'medium'
        })
        console.log(errors, newTodo);
      }}>Create </button>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.content}</li>
        ))}
      </ul>
    </main>
  );
}

拡張: Hooks

Hooks は、拡張性を付与する実装ツールです。拡張性とは、カスタマイズ、スケーラビリティ、将来を見据えたもの、効率性、そしてコラボレーションを意味します。

Amplify Functions は AWS Lambda に支えられており、サーバーサイドの拡張性を提供します。基盤となるインフラストラクチャを管理せず、アプリケーションにバックエンド機能を拡張できます。Amplify Gen 2 により、開発者は Amazon Cognito でのユーザー認証など、さまざまなイベントに対応するカスタム機能を作成できます。顧客がサインアップした後にビジネスロジックを実行したい状況を考えてみましょう。開発者は Amazon Cognito で post-confirmation トリガーを作成し、カスタムサーバーレス関数で認証フローを強化できます。

// amplify/backend.ts
import * as url from 'node:url';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource.js';
import { data } from './data/resource.js';

const backend = defineBackend({
  auth,
  data
});

// create function stack for the auth triggers
const authTriggerStack = backend.createStack('AuthTriggerStack');

// create the PostConfirmation trigger
const postConfirmationTrigger = new lambda.NodejsFunction(
  authTriggerStack,
  'PostConfirmation',
  {
    entry: url.fileURLToPath(
      new URL('./custom/post-confirmation.ts', import.meta.url)
    )
  }
);

// add the newly created trigger to the auth resource
const userPool = backend.resources.auth.resources.userPool as cognito.UserPool;
userPool.addTrigger(
  cognito.UserPoolOperation.POST_CONFIRMATION,
  postConfirmationTrigger
);

Amplify Hub イベントはフロントエンドの拡張性を提供します。これにより、フロントエンドアプリケーションのさまざまな部分がリアルタイムで相互通信できるようになります。これは、アプリケーションの状態の変化やユーザー操作に迅速に対応する必要がある、動的で応答性の高いユーザーインターフェイスを作成する場合に特に役立ちます。たとえば、ユーザーがログインすると、フロントエンドのさまざまなコンポーネントがこのイベントに直ちに応答し、それに応じて UI を更新できます。

import { Hub } from "aws-amplify/utils";

const hubListenerCancel = Hub.listen("auth", ({ payload }) => {
  switch (payload.event) {
    case "signedIn":
      console.log("user have been signedIn successfully.");
      break;
    case "signedOut":
      console.log("user have been signedOut successfully.");
      break;
    case "tokenRefresh":
      console.log("auth tokens have been refreshed.");
      break;
    case "tokenRefresh_failure":
      console.log("failure while refreshing auth tokens.");
      break;
    case "signInWithRedirect":
      console.log("signInWithRedirect API has successfully been resolved.");
      break;
    case "signInWithRedirect_failure":
      console.log("failure while trying to resolve signInWithRedirect API.");
      break;
    case "customOAuthState":
      logger.info("custom state returned from CognitoHosted UI");
      break;
  }
});

hubListenerCancel(); // stop listening for messages

Amplify Gen2 において、Hooks (サーバーレス関数と UI イベント) は後回しにされるものではなく、意図的にソリューションに組み込まれています。この統合により、開発者は順応性と拡張性に優れ、将来を見据えたアプリケーションを作成するためのツールを手に入れることができます。Amplify Hub イベントによるリアルタイムのインタラクションでフロントエンドを強化するにしても、サーバーレス機能でバックエンドを拡張するにしても、Amplify Gen2 は、現代のアプリケーション開発の限界を押し広げる柔軟性と能力を開発者に提供するように設計されています。

拡張: サービス

開発者は、新しいサービスをアプリケーションに統合するよりも、ソリューションの構築に時間を費やすことを好みます。一般的に、AWS サービスのオーケストレーションをアプリケーションコードに組み込む必要がある場合、多くのコンテキストスイッチが必要です。これらのサービスは手動で作成および管理する必要があり、各サービスの制約を事前に理解しておく必要があります。

この問題を解決し、開発者のエクスペリエンスを向上させるために、Amplify Gen2 は AWS Cloud Development Kit (CDK) の上に階層化されています。つまり、Amplify が生成するリソースを拡張するのに特別な設定は必要ないということです。たとえば、CDK を使用して LocationMapStack というカスタムスタックを作成し、アプリケーションに必要な Amazon Location Service リソースを定義できます。その後、このスタックを amplify/backend.ts ファイルに含めることで、アプリケーションの一部として確実にデプロイできます。

import { CfnOutput, Stack, StackProps } from "aws-cdk-lib";
import * as locations from "aws-cdk-lib/aws-location";
import { Construct } from "constructs";

export class LocationMapStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Create the map resource
    const map = new locations.CfnMap(this, "LocationMap", {
      configuration: {
        style: "VectorEsriStreets", // map style
      },
      description: "My Location Map",
      mapName: "MyMap",
    });

    new CfnOutput(this, "mapArn", {
      value: map.attrArn,
      exportName: "mapArn",
    });
  }
}

// In amplify/backend.ts

const locationStack = backend.createStack("locationStack");

new LocationMapStack(locationStack, "locationstack");

これの最大の利点は、コードエディターを離れる必要がないことです。Amplify の機能を既存の AWS リソースと統合できるため、既存のインフラストラクチャを Amplify の機能とともに活用できます。また、CDK が型を持っているため、Intellisense から洞察を得ることができ、サービスのドキュメントを事前に読む必要はありません。

デリバリー: Time to Production(TTP)

Qovery(トップクラスの社内開発者プラットフォーム)は、”Overcoming Shared Environment Bottlenecks“で、かつてはコラボレーションに効果的だった共有開発環境が、今では生産性のボトルネックを引き起こすことで知られていると書いています。このような環境では、プロジェクトの停滞や開発チームの妨げとなる競合やリソース競合が発生することがよくあります。4 人の開発者が単独でフルスタックの機能に取り組んでいるが、1 つの開発環境を共有しているところを想像してみてください。このような共有環境は、ストレスや遅延の原因となります。各開発者がそれぞれのコンポーネントに変更を加える際には、競合を避け、変更によってアプリケーション全体が損なわれないように、チームメイトと慎重に調整する必要があります。

このシナリオでは、開発者 A はバックエンドコードを熱心に更新しますが、開発者 C がデータベースの変更を完了するまで待つ必要があります。一方、UI を全面的に見直した開発者 B はジレンマに直面しています。コードをプッシュする必要があるが、開発者 D の進行中の統合作業とのコンフリクトの可能性には警戒しているということです。この共有開発環境は、コラボレーションを促進するどころか、進行を妨げ、開発サイクル全体を遅らせるボトルネックになります。

Amplify Gen 2 では、開発者ごとのクラウドサンドボックス環境が導入されています。ローカル開発向けに調整されたこれらの環境では、忠実度の高い AWS バックエンドがリアルタイムでデプロイされるため、開発者は本番環境に近い開発環境で機能を繰り返し開発できます。

このアプローチは、チームがクラウドアプリケーションで共同作業したり反復したりする方法に革命をもたらし、開発時間を大幅に短縮し、本番稼働までの時間(TTP)を短縮します。このプロセスのシンプルさも同様に画期的です。コードの変更を保存するだけで、コードをサンドボックス環境に簡単にデプロイできます。この 4 人の開発者が、お互いの変更によって環境が乱されることなく、フルスタックの機能を個別にシームレスに開発しているところを想像してみてください。

デリバリー: インラインバックエンド

従来のウェブ開発では、フロントエンドとバックエンドの両方のコンポーネントをデプロイするには、多くの設定と複数のステップが必要であり、ストレスの原因となる可能性があります。インラインバックエンドは、フロントエンドとバックエンドを別々にデプロイする必要がないため、デプロイプロセスを合理化し、アプリケーションを本番環境に移行するのに必要なステップを大幅に削減します。この合理化されたアプローチにより、開発者の時間と労力が節約され、デプロイエラーのリスクが最小限に抑えられるため、より信頼性が高く一貫性のある開発環境が保証されます。

Amplify Gen 2 では、コードをコミットするたびにフロントエンドとバックエンドをまとめてデプロイできます。コードファーストアプローチでは、Git リポジトリがフルスタックアプリケーションの状態の信頼できる情報源となることが保証されます。バックエンドのリソースはすべてコードとして再定義され、ブランチ間の再現性と移植性が促進されます。これと環境変数とシークレットの一元管理を組み合わせることで、下位環境から上位環境への昇格ワークフローが簡素化されます。Amplify Gen 2 は、単一プロジェクトのデプロイアプローチを導入し、Git ブランチを共有環境として活用し、コードファーストの理念を採用し、環境変数とシークレットの一元管理を実装することで、フルスタックのアプリケーションデプロイに革命をもたらします。

デリバリー: TypeScript でインフラストラクチャを追加する

AWS Amplify Gen 2 はこの変化を利用して、「使い慣れたコードのみ」をモットーとする新しい方法論を提供しています。このアプローチにより、従来のインフラストラクチャ管理ツールや言語に付随する手間が省け、フルスタックの開発者は使い慣れた TypeScript を使用してアプリケーションインフラストラクチャを定義できるようになります。

これまで、インフラストラクチャをセットアップするには、AWS コンソールをナビゲートするか、多数の CLI コマンドを実行する必要がありました。その結果、セットアップの予測が難しくなり、エラーが発生しやすくなることがよくありました。Amplify Gen2 では、Amplify がこのプロセスを変革し、インフラストラクチャの設定を予測可能にし、データ、認証、ストレージサービスなどをサポートするコードと同じ場所に配置できるようになりました。この変更により、インフラストラクチャーの複雑さが制御不能になり、予測不能の原因になりかねないという大きな課題が解決されます。

パスベースの規約 (amplify/auth/resource.tsamplify/data/resource.ts など) に従って TypeScript としてインフラストラクチャをファイルに作成することで、開発者は Visual Studio Code の厳密な型指定と IntelliSense を活用してエラーを最小限に抑えることができます。バックエンドに重大な変更があった場合、フロントエンドコードで型エラーが発生し、開発者へ即座にフィードバックされます。このフィードバックループによってフロントエンドコードの信頼性を確保し、開発者が自信を持って開発を進めることができます。

import { defineAuth } from "@aws-amplify/backend";
import secret from "@aws-amplify/backend";

export const auth = defineAuth({
  loginWith: {
    email: {
      verificationEmailSubject: "Welcome, verify your email",
    },
    externalProviders: {
      loginWithAmazon: {
        clientId: secret("LOGINWITHAMAZON_CLIENT_ID"),
        clientSecret: secret("LOGINWITHAMAZON_CLIENT_SECRET"),
      },
    },
  },
  multifactor: {
    mode: "OPTIONAL",
    sms: {
      smsMessage: (code) => `Your verification code is ${code}`,
    },
  },
  userAttributes: {
    profilePicture: {
      mutable: true,
      required: false,
    },
  },
});

Amplify Gen2 の TypeScript のインフラストラクチャは、「設定より規約」の原則を体現しており、インフラストラクチャを管理するための明確で体系的な方法を提供します。つまり、開発者はコードエディターで快適に操作でき、その場でドキュメントにアクセスでき、コンテキストの切り替えによる中断を回避できます。

まとめ

AWS Amplify のコードファーストアプローチは、カスタマーエクスペリエンスを優先し、AWS サービスを活用することで、ウェブ開発に革命をもたらしています。このアプローチにより、開発者は顧客のニーズを満たす堅牢でスケーラブルなフルスタックアプリケーションを作成できます。

このアプローチの主なメッセージには、コードからのインフラストラクチャ機能、インフラストラクチャを定義するコードを記述する機能、フルスタック開発用の TypeScript、保存時の変更のプレビュー、コード生成、設定不要のフルスタックデプロイなどがあります。これらのパラダイムを採用することで、開発者は TypeScript を使用してアプリケーションを構築し、迅速に開発サイクルを反復して、アプリケーションを簡単にスケーリングできます。AWS Amplify のコードファーストアプローチは、開発者に強力なツールとシームレスな開発体験を提供することで、ウェブ開発の未来を形作っています。

この記事は Christian Nwamba による “The future of web development: AWS Amplify’s Code First Approach” を和訳したものです。翻訳はソリューションアーキテクトの髙柴が担当しました。