Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

ガラパゴスはiOSDC 2022を応援しています #iOSDC

AIR Design for Apps 事業部 アプリエンジニアチーム iOSユニットの亀澤です。 夏期休暇は9月のシルバーウィークにとろうかと思ってます。

ガラパゴスiOS DC Japan 2022のスポンサーに参加します。

iosdc.jp

私が今年入社直後の2022年4月にiOS DCに参加したいという話があり検討していました。 iOSユニットとしてブース出展やスピーカーとしても参加しいと検討している中、申し込みを決めた時点で既にスピーカー、ブース出展共に締め切られていたので残念ながらそちらの方は今回参加できませんでした。 来年はブース、スピーカーともに参加できるように準備を進めていきたいと思います。

さて、今回はゴールドスポンサーとして参加していますが、スピーカーの登壇、ブース出展もありません。 そんなにすることは無いと思っていましたが、それなりにスポンサーとしてのの準備がありました。

  1. PR文
  2. ノベルティ
  3. パンフレット原稿
  4. 支払い
  5. トークン納品

申し込み自体が遅かったため申し込み後にやらなきゃいけない期日もギリギリです。 人事、デザイン、広報と巻き込んで事業部マネージャーが考えた文章を元にPR文を作成しました。

ノベルティは納期等を考えてステッカーとクリアファイルに。 元のイメージだけエンジニアが考えてデザイナーにラフでサインをいくつか作ってもらい投票で決定しました。 パンフレットもデザイナーさんにがんばってもらい納品することができました。 支払いは既に経理に申請してあるとのことでしたが、途中どうなったかわからず、窓口担当としてはここまで進んで支払いが遅れるんじゃないかとドキドキでした(その後、無事に担当者から「振り込みましたよ」と連絡をもらい一安心でした)。 トークン納品はチャレンジトークンイベントのトーク

いよいよ来月に開催が迫ってきました。今回は数年ぶりにオフラインでも開催されるので楽しみですね!

アプリエンジニアチームではDroidKaigiも参加を目指してAndroidユニットのメンバーががんばってます。 そちらの方もご期待下さい。

ここまで読んでいただいた方、ガラパゴスのPR文言は「正気のアプリ制作」です。 正気とか掲げている会社ってどんなことをしているの?と疑問に感じた方はこちらを読んでいただきたい。

www.wantedly.com

そして、ガラパゴスではエンジニア募集しています! 正気のアプリ制作に共感を持った方のご応募お待ちしています。

www.glpgs.com

MSWでAPI開発前にフロントエンド開発を行う

AIR Design for Marketing 事業部 フロントエンドエンジニアのHashigoです。

私は2022年1月に入社する以前は、主にtext/htmlをレスポンスとして受け取ってコーディングを行うフロントエンド環境で開発を行っていました。

現在、私が参加しているプロジェクトではReactで開発を行なっており、且つCSR(Client Side Rendering)のみのSPAであるため、初期表示の時、React側でGETのHTTPリクエストを行うことが多々あります。その場合、初期表示を行うだけでも「待機」「成功」「失敗」という状態をコンポーネント側で持つ必要があります。

text/htmlを受け取るフロントエンド開発では、初期値はサーバーサイドから既に受け取った状態であることが多く、「待機」=「ブラウザのローディング」で、「失敗」の場合もサーバーサイドから返ってくるhtmlがそもそも違うので、フロント側で考慮することがあまりありませんでした。そして、そういった場合の開発では、フロントエンドを先行で開発していても、初期表示に関しては直書きの値を変数に置き換える作業で終止することが主だったと記憶しています。

React(SPA)では、フロントエンドを先行して開発する場合に、変数に当たる箇所を直書きすると、いざAPIからデータを取得するコードを書いた時に、API通信の状態をフォローできていなくて、APIと繋ぎ込む実装が思ったよりも工数がかかってしまった、ということがありました。

そこで、いろいろな記事で紹介されているMSW(Mock Service Worker)を使用して、API開発前にフロントエンド実装を行ったところ、開発体験が良く、実際のAPIを繋ぎ込む作業が以前より楽に行えました。また、私はAPI開発をしたことが無いので、API開発の触りを学ぶという意味でもとても良い経験でした。

今回は、API通信の状態を考慮できるようにUI開発の下地としてMSWを使ってHTTPリクエストのモックを作成したいと思います。

Mock Service Worker

mswjs.io

執筆時点のバージョンはv0.44.2です。

1 MSWでHTTPリクエストのモックを定義

ドキュメントにはセットアップや各APIについて丁寧に解説されていますので、こちらに沿って進めていきます。

今回はREST API & Create React Appを想定したコードを記述しています。

1-1 MSWのインストール

npm install msw --save-dev
# or
yarn add msw --dev

こちらでpackage.jsonに追加されます。

1-2 モックの定義

モックを定義するためのファイルを格納するディレクトリを作成します。

mkdir src/mocks

次にHTTPリクエストのハンドラーを書くファイルを作成します。

touch src/mocks/handlers.ts

今回は、色の名前とカラーコードのリストを取得するAPI(GET)、新しい色をリストに追加するAPIのモック(POST)を作成します。そのために、レスポンス用のデータを用意します。

// src/mocks/data/colors.json
[
  {
    "id": "uuid-1",
    "name": "Red",
    "code": "#ef4444"
  },
  {
    "id": "uuid-2",
    "name": "Orange",
    "code": "#fb923c"
  },
  {
    "id": "uuid-3",
    "name": "Yellow",
    "code": "#facc15"
  },
  {
    "id": "uuid-4",
    "name": "Green",
    "code": "#22c55e"
  }
]

次にhandlers.tsにHTTPリクエストを処理するための関数を書きます。

まずは、先ほどのJSONをimportし、色の一覧を取得するGETリクエストのハンドラーとリゾルバーを作ります。
(別途JSONファイルを用意する必要はなく、直接オブジェクトを書いても大丈夫です。)

// src/mocks/handlers.ts
import { rest } from "msw";
import colors from "./data/colors.json";

export const handlers = [
  rest.get("/mock/colors-list", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(colors));
  }),
]

MSWドキュメン上では、rest.get()をhandler、handlerの第二引数のコールバック関数をresolverと呼んでいます。

resolverに渡される3つの引数は、順番に下記のようになっています。

  • request(req) → リクエストに関する情報が入ったオブジェクト
  • response(res) → モックされたレスポンスのオブジェクトを作成する関数
  • context(ctx) → レスポンスの内容を変換する関数群が入ったオブジェクト

続けて、新しい色をリストに追加するPOSTリクエストを作ります。
ついでに、同じカラーコードがあった場合はエラーを返すようにしたいと思います。

// src/mocks/handlers.js
import { rest } from "msw";
import colors from "./data/colors.json";

// 追記
type ReqBody = {
  name: string;
  code: string;
};
type ResBody = {
  id: string;
  name: string;
  code: string;
};

export const handlers = [
  rest.get("/mock/colors-list", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(colors));
  }),

  // 追加
  rest.post<ReqBody, ResBody>("/mock/create-color", async (req, res, ctx) => {
    const { name, code } = await req.json();

    if (colors.find((color) => color.code === code)) {
      // 重複するカラーコードがあった場合はエラー
      return res(
        ctx.status(409),
        ctx.json({
          errorMessage: `${code}は既に登録されています。`,
        })
      );
    };
    
    return res(
      ctx.status(201),
      ctx.json({
        id: "uuid-5",
        name,
        code,
      })
    );
  }),
]

requestとresponseの各bodyの値の型付けと、request bodyの取得方法に悩みましたが、
resolver関数の第二引数をasyncにするなど、下記のようににすることで、無事に取得できるようです。

rest.post<ReqBody, ResBody>("/mock/create-color", async (req, res, ctx) => {
    const { name, code } = await req.json();

書き方については、下記の記事とMSWトップページのTypeScriptバージョンが参考になりました。

dev.to

※req.bodyはエディタ上で非推奨と表示され、MSWドキュメントにも記述がありませんでした。

2 MSWで定義したモックAPIを動かす

2-1 MSWを初期化

npx msw init <PUBLIC_DIR> --save

# Create React Appの場合はこちら
npx msw init public/ --save

<PUBLIC_DIR>の部分はプロジェクトごとにビルドされて公開されるディレクトリを指定します。

使っているフレームワークやライブラリによる違いは下記で確認できます。

Browser - Getting Started - Mock Service Worker Docs

2-2 Service Workerを起動させるためのファイルを作成

touch src/mocks/browser.ts

続いて、作成したbrowser.tsに下記を記述してリクエストハンドラーを渡してService Workerインスタンスを作成します。

// src/mocks/browser.ts
import { setupWorker } from "msw";
import { handlers } from "./handlers";

// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers);

setupWorker関数はリクエストハンドラーを受け取り、作成したService Workerインスタンスを操作するメソッドを返してくれるそうです。

setupWorker() - Api - Mock Service Worker Docs

2-3 Service Workerを起動

It's not recommended to include Mock Service Worker in production. Doing so may lead to a distorted experience for your users.

https://mswjs.io/docs/getting-started/integrate/browser#start-worker

上記注意書きのように、production環境では動作させないように先ほどのworkerインスタンスをimportして、Reactのindex.tsでService Workerをアクティブにします。

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

if (process.env.NODE_ENV === "development") {
  const { worker } = require("./mocks/browser")
  worker.start()
}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

ここでようやく、Create React Appを起動します。

npm start
# or
yarn start

localhostが立ち上がったら、ブラウザのdevtoolでコンソールを確認します。

MSW Mocking enabled.

こちらが表示されていれば、無事に立ち上げ完了です 🎉

ちなみに、yarn buildなどプロダクションビルドすると、上記は表示されず、Service Workerが有効では無いことが分かります。

3 UIからHTTPリクエストをモックしてみる

3-1 まずは、GETリクエストで一覧を取得してみる

下記のようなコードでMSWで定義したAPIにfetchしてみます。

const [colors, setColors] = useState([]);

useEffect(() => {
  getColors();
}, []);

const getColors = async () => {
  try {
    const response = await fetch("/mock/colors-list");

    if (!response.ok) {
      throw new Error("色の一覧取得に失敗しました");
    }

    const colors = await response.json();
    setColors(colors);

  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
};

すると、ブラウザdevtoolのネットワークを見ると、画像のようにちゃんとレスポンスが返ってきています。

Google Chromeデベロッパーツールのネットワークタブ

Google Chromeデベロッパーツールのネットワークタブ

3-2 POSTリクエストで新しいデータを追加してみる

下記のコードでformからPOSTメソッドでfetchしてみます。

const [colors, setColors] = useState([]);

const handleSubmit = async (e: React.SyntheticEvent) => {
  e.preventDefault();

  // formから取得したinputのvalue
  const target = e.target as typeof e.target & {
    name: { value: string };
    code: { value: string };
  };
  const name = target.name.value;
  const code = target.code.value;

  try {
    const response = await fetch("/mock/create-color", {
      method: "POST",
      body: JSON.stringify({
        name,
        code,
      }),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.errorMessage);
    }

    const newColor = await response.json();
    setColors([...colors, newColor]);

  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
};

return (
  <div className="App">
    {colors.length !== 0 && (
      <ul>
        {colors.map((color, index) => (
          <li>
            <dl>
              <dt>{color.name}</dt>
              <dd>{color.code}</dd>
            </dl>
          </li>
        ))}
      </ul>
    )}
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" id="color-name" />
      <input type="color" name="code" id="color-code" />
      <button type="submit">追加</button>
    </form>
  </div>
);

input type=”name”input type=”color”に値を入れてsubmitすると、画像のようにsubmitしたデータが追加され、レスポンスが返ってきました。

Google Chromeデベロッパーツールのネットワークタブ

Google Chromeデベロッパーツールのネットワークタブ

Google Chromeデベロッパーツールのネットワークタブ

さらに、存在するカラーコード(#22c55e)を入れてPOSTリクエストしてみます。

Google Chromeデベロッパーツールのネットワークタブ

Google Chromeデベロッパーツールのネットワークタブ

Google Chromeデベロッパーツールのネットワークタブ

定義した通り、403エラーでエラーメッセージをレスポンスとして返すことができました。

HTTPリクエストがモックできた後は

MSWでモックAPIを作れたので、「待機」「成功」「失敗」を意識してコードを書くことができるようになりました。

下記のようにstateによってreturnするJSXを出し分けることで、各状態のUIを実装することができます。

const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");

if (loading) {
  return <div>Loading...</div>
}

if (error) {
  return <p>{error}</p>
}

return <div>成功した時のDOM</div>

Succeeding Through Teamwork

It is literally true that you can succeed best and quickest by helping others to succeed”, a quote by American writer Napoleon Hill and author of one of the 10 best-selling self-improvement books of all time, Think and Grow Rich. First published in 1937, the book covers a variety of philosophical business practices that - even almost 90 years after its initial publication - are still considered as one of the most important fundamentals for a person, team, or business to succeed. And yes, it can be argued that success isn’t guaranteed by simply following the most basic of guidelines; Books, people, and even the Internet (despite its vast amount of available information) are not always right - it is what you do with it to succeed.

The big question is: How would you describe or measure success? In this day and age, digital evolution has become such a big part of our daily lives, that the simplest of things, like shopping and streaming movies are now mostly done online. Meetings, remote work, and even big company events follow the same pattern. Transitioning to a digital age can or possibly should be considered a necessity, though not always as successful as one might hope. For smaller IT teams this type of change has definitely created its own set of difficulties - success isn’t always visible, but definitely measurable.

Our team currently consists of four smaller core teams: directors, designers, developers, and testers. Though each team has its own responsibilities, to succeed as a whole it is important to understand what each team does to define its own success and how their values play a role in this. Starting with our director team, it is needless to say that direct contact with the client is of high significance. Without a deeper understanding of what the client wants for its business, success cannot be achieved - it all starts with mutual respect and agreement. Similarly, the work that the director team is responsible for, has a direct effect on all the other teams. The designer cannot create a visual representation without an idea; The developer cannot build without specifications and designs; The tester cannot finish the project without the previous parts.

To ensure that the director’s work does not create any hiccups further down the line - and possibly hinder the success of the project - there are several protocols in place. Initially, the content of a new release is privately discussed between the director and designer, this to make sure what is doable within a specific timeframe. In case of a bigger release, a rough outline is shared with the design team. They create a prototype (often a variety of different designs) and share this with the other teams. Since each core team has a completely different perspective on how to use the design and whether or not it needs improvements, a prototype presentation session is held before the team takes it to the next step: sharing the prototype with the client.

The client may or may not like the design, or could even ask the designer to add changes so that it comes closer to what the company is aiming for. This also provides the director team with most of the information it needs for the technical specifications, whilst the design team focuses on the final design. Once a reviewable version of the specifications is available, each core team takes a look at the contents and provides feedback. For developers this could be related to changes to the existing code or technical matters, and for testers about testability or unusual cases in which the implementation could lead to issues. Since this whole process takes place prior to development, communication between each team is crucial.

Development and test are two equally different beasts. For example, our team consists of several developers and quality engineers to make sure the new version of the application gets exactly what was agreed on. This could be a set of small changes, or bigger additions like new screens, animations, or functionalities that would further improve the user-experience. On paper, the development team is located right in the middle, not only responsible for communication with both the director and designer team in case of further clarification or - if necessary - slight adjustments, but can also act as the direct point of contact for technical issues or questions from the quality assurance team. Once development has come to an end, the testers take over and start the final sprint towards release. Assuring a perfect and bug-free product is - in almost all cases - not possible, but through constant feedback and consultation it is a lot more likely that one team’s victory will be a step closer to measuring the success of the group. People are intrinsically purpose-oriented and believe-driven, ergo deciding where and how to start is equally important as understanding what each team believes in and what it does to reach the finish line.

To come to a close, I believe it is essential to understand the difficulties each team may struggle with. Difficulties create hurdles, and hurdles lower the speed at which you progress. Without progress you can not bring it to a successful conclusion. Though it is completely understandable that each core team is and should be in charge of its own success, not sharing any of it with the others only leads to confusion and possibly less motivation. For instance, animals follow a similar pattern. A perfect example would be wolves, where the pack is divided into smaller groups. Each smaller group has a purpose, with the one in front (the strongest) responsible for creating a path and protecting the weakest, and the one in the middle (the older ones) and the one in the back (the leader) for keeping the pack together. Trust and communication are key, as long as each individual group knows what to do to help the others succeed. “If everyone is moving forward together, then success takes care of itself.