フロントエンドエンジニアがバックエンド開発に挑戦して得た学び

  • URLをコピーしました!

こんにちは。開発部で主にフロントエンドの開発を行なっている古園(@miyabin4113)です。

普段はプレスリリースを作成するエディターに関する業務に携わっています。

今回なぜフロントエンドエンジニアがバックエンド開発に挑戦したかと言いますと、以前からバックエンド領域に興味があったことが大きいです。

元々大学院ではゼロからサーバーを立てて研究開発用の環境を作成したり、個人開発でTwitter APIを用いたWebアプリを作成したりといった経験はあったのですが、業務でバックエンドを触ったことは一度もありませんでした。

このようなバックグラウンドがあったため、将来的には業務でもバックエンドを触りたいと考えていました。

目次

実装した内容について

現在、PR TIMESのエディターは現エディターを今年12月に廃止する方向で動いています。

その代わりとなる新エディターは、

  • 文章の間違いなどを指摘してくれる校正機能
  • 今まで作成したプレスリリースの中から特定のプレスリリースを探しやすくする検索機能

など大勢の方に満足して扱っていただけるエディターを目指し、日々開発を続けています。

そんなエディターの機能の1つにプレスリリースを公開する際、その内容を自分で選んだメディアに向けてメールやFAXで送信することができます。そのメディアの連絡先をまとめたものをメディアリストといいます。

ただし、記事配信を行う段階でメディアリストを選択する際にそのリストの中にはメールのみ登録されている企業しかないのか、またはFAXのみ登録している企業しかないのか、はたまたどちらも登録している企業が混ざっているのか。その判別をつけることができず全て「メール」と表記されてしまっているといったバグがありました。

これを解消すべく、APIに「メールのみ」「FAXのみ」「メールとFAXどちらもある」の表示を出し分けられる情報を追加する作業を行うことにしました。

バックエンド側の実装

SQL改修

メディアリストを返すAPIにメールのみ登録されている企業しかない場合にtrueが入るフラグ(以降「メールフラグ」)

とFAXのみ登録されている企業しかない場合にtrueが入るフラグ(以降「FAXフラグ」)を追加しました。

弊社ではDBから情報を取ってくる際はPostgreSQLを使用しているため、 BOOL_OR 関数を使用して下記のように値が Null や空文字( ’’ )でない場合はtrueを返すという実装を行いました。

BOOL_OR(mail IS NOT NULL AND mail <> \'\') AS is_mail
BOOL_OR(fax IS NOT NULL AND fax <> \'\') AS is_fax

テスト作成&確認

弊社のバックエンドのテストはPHPUnitを使用して作成しています。

今回はそれに加えてPHP環境で配列の構造を明示的に示すために使用されるArray Shapeを用いることでTypeScriptのように型安全で実装を行いました。

例として以下のようなArray Shapeで型定義されたSeederを用意し、

<?php
declare(strict_types=1);

namespace Seeder;

class DataSeeder
{
    /**
     * クラスに渡す値の型を定義
     *
     * @param PDO $pdo
     * @param Generator $faker
     * @param array{
     *     media_id?: int,
     *     fax?: string,
     *     mail?: string,
     * } $data
     *
     * @return void
     */
    public static function seeder(PDO $pdo, Generator $faker, array $data): void
    {
        foreach ($data as $item) {
            PrSql::execute($pdo,
                /** @lang PostgreSQL */
                '
                INSERT INTO hoge_db (
                    media_id,
                    fax,
                    mail,
                ) VALUES (
                    :media_id@int,
                    :fax@string,
                    :mail@string,
                )
                ',
                [
                    ':media_id' => $item['media_id'] ?? $faker->numberBetween(0, 999999),
                    ':fax' => $item['fax'] ?? undefined,
                    ':mail' => $item['mail'] ?? undefined,
                ]
            );
        }
    }
}

それをテストファイルで使用して行いたいテストを実行します。

<?php

// ...

class CheckFaxMailMediaTest extends TestCase
{
    private PDO $pdo;
    private Generator $faker;

    private const TEST_COMPANY_ID = 1;
    private const TEST_MEDIA_ID = 1;

    public function setUp(): void
    {
        parent::setUp();
        $this->pdo = PDOFactory::getPDOInstance();
        $this->faker = Factory::create("ja_JP");

        $this->resetData();
    }

    public function resetData(): void
    {
        // test用のDBデータ初期化
        $this->pdo->exec('TRUNCATE TABLE fuga_db CASCADE');

        // このSeederはテストを成り立たせるために用意する必要がある
        CommonSeeder::seed(
            $this->pdo,
            $this->faker,
            [
                'company_id' => self::TEST_COMPANY_ID,
            ]
        );
    }

    public function test_メディアリストの中にmailが登録されているメディアがある場合():void
    {
        // ここでmailに値を設定する
        DataSeeder::seed(
            $this->pdo,
            $this->faker,
            [
                'media_id' => self::TEST_MEDIA_ID,
                'fax' => $this->faker->phoneNumber,
                'mail' => $this->faker->email, // mailが登録された状態
            ],
        );

        $data = MediaList::getData(self::TEST_COMPANY_ID, $this->pdo);

        $this->assertTrue($data[0]->is_mail, 'The is_mail property of the first element should be true.');
    }
    
    // 以下続く...
}

フロントエンド側の実装

PR TIMESのフロントエンド環境ではOpenAPIを用いて型定義を行なっています。

そのため、今回バックエンド側で追加した内容をymlファイルに記載する必要があります。

media_list:
    ...
    properties:
        ...
        is_mail:
            type: boolean
            description: リストの中にFaxが登録されている企業がある
            example: true
         is_fax:
            type: boolean
            description: リストの中にFaxが登録されている企業がある
            example: true
    required:
        ...
        - is_mail
        - is_fax

続いて、サイト側でAPIから帰ってきた情報を元にメール・FAX、メール、FAXの表示出し分けて表示します。

エディターのフロントはReactで実装を行なっているので以下のようにtsxファイルで渡されたフラグに応じて表示の出し分けを行います。

// tsxファイル
<span>
  {checkMediaListType(
    mediaList.isMail, // APIから返ってきたis_mailの値が入った変数
    mediaList.isFax,  // APIから返ってきたis_faxの値が入った変数
  )}
</span>
const checkMediaListType = (isMail: boolean, isFax: boolean) => {
  if (isMail && isFax) {
    return 'メール・FAX';
  }

  if (isMail && !isFax) {
    return 'メール';
  }

  if (!isMail && isFax) {
    return 'FAX';
  }

  // メディアリストの登録企業数が0件の場合も考慮
  return '';
};

学びになったこと

今回バックエンドでテストを書いたことで、フロントエンドでテストを書く際にも良い影響が出るようになりました。

今までは、ページのボタンなどの動きが発生するものやユーザーが行うであろう動作に着目してテスト設計を行なっていたのですが、APIから返ってくるデータをもとに設計を行うことで「フロント側はテストが行いやすい実装ができているか」が気になるようになり、新しく関数を作成する際に関数内にReactに依存する変数が使用されていない場合はReactのファイルの中ではなく外部に置く設計にしてテストを行いやすくする、といったフロントエンドのコードの可読性や保守性を向上させることに対しても考える力がついたと感じています。

また、個人的に型安全に実装するのが好きなので、PHPでもそれは可能という新鮮な発見があったりと、自分の知識をアップデートしつつ楽しみながら開発を行うことができました。

今後も機会を見つけてバックエンド実装に挑戦したいと思います。

まとめ

1つのことに注力してスペシャリストを目指すのももちろん良いですが、自分が知らない領域に挑戦することもそれとは違った楽しさや経験があり良い刺激になります。

また、今回こちらの記事を書いたのは、以前バックエンドエンジニアがフロントエンド領域に挑戦した話が公開されたことも大きいです。今回はその逆パターンになります。

あわせて読みたい
バックエンドエンジニアから見たフロントエンド開発の魅力と学び こんにちは。開発本部で主にバックエンドの開発をしている ueeda です。 PR TIMES Web クリッピングにおいて、新機能「クリッププレビュー」を実装しました。この実装を...

最後に、PR TIMESではフロントエンドとバックエンドの垣根を作らず、挑戦したい人を応援し、気軽に質問やペアプロができる環境が整っています。

新卒・中途どちらも採用を行なっておりますので、こういった別の領域に挑戦したい方は是非興味を持っていただけると幸いです!

あわせて読みたい
あわせて読みたい
  • URLをコピーしました!

この記事を書いた人

株式会社PR TIMES 開発部 フロントエンドエンジニア

目次