スタイル・エッジ技術ブログ

士業集客支援/コンサルティングのスタイル・エッジのエンジニアによるブログです。

【モダン開発 #3】TDDに挑戦してみた!

はじめに

こんにちは!スタイル・エッジの ike です。 過去何回かに渡って記事を連載していましたが、モダン開発#2までを執筆していた SHISO さんからバトンパスを受けて、今回は実装者としてプロジェクトに参画した視点から執筆します!

弊社ではある業務システムの開発プロジェクトにて、テスト駆動開発(TDD)に挑戦しました。しかし、結果的にはチームメンバーの経験の少なさ故の難しさがあり、途中でユニットテストの導入を最優先とした方針に転換することになりました。
そこで、TDDに挑戦した経緯や所感、途中で方針転換することになってしまった理由、その中で得られた恩恵・気付きを振り返っていきます!

TDD の挑戦に至った経緯

弊社で保守開発を行なっているあるプロダクトでは、改修時に生じたバグの解消に時間を費やしてしまい、顧客満足度の向上に直接つながるような改善に時間を割けないという問題がありました。

このプロダクトは業務ロジックが非常に複雑なのにも関わらずユニットテストがなかったり、コード設計自体が綺麗とは言い難いという状況になっており、無自覚でバグを埋め込みやすい状態になってしまっていました。また、仕様を詳細に知っているのが一握りのメンバーに限られてしまい、改修後の動作検証で考慮漏れによる手戻りなどもありました。

この経験から、新規プロダクトでは以下のようなTDDのメリットを享受しようと考えました。

  • 黄金の回転*1に含まれるリファクタリングにより、必然的にコード品質が担保される
  • 自然と高凝集・低結合で多くの部品で構成された設計を行うようになる
  • 小さな単位でテストと実装を繰り返すことで、実装時の認知負荷を軽減できる
  • 達成すべきことが明確になった状態で実装に取り組むことができる
  • 複雑な仕様がテストコードという形で確実に残る

いざ挑戦してみて感じたギャップと方針転換

上記のようなメリットを享受するためにTDDに挑戦はしてみたものの、チーム内にテスト実装自体の経験者が少なかったことや、実装工数とコードレビュー工数が想定以上に重かったことなどから、徐々にプロジェクトとしての余裕がなくなっていきました。これにより次第にテストファーストで書くことが難しくなり、TDDの黄金の回転もうまく行えなくなっていきました。

プロダクトのリリース期限との兼ね合いから、開発手法自体は従来と大きく変えずに「主要部分にユニットテストを導入すること」に照準を合わせることになりました。

ユニットテスト導入による恩恵

コードの可読性・機能拡張性が高まった

ユニットテストの実装を行うためには、テスタブルなコードにする必要があるため、自ずとクラスやモジュールの粒度のばらつきがなくなり、高凝集・低結合なコードになっていきました。また、各メソッドに単一の責務のみを持たせるようになることで、コードの複雑さを取り除くことができました。これらにより変更時の影響範囲が把握しやすく、後から機能を追加することも容易になりました。

重要な業務ロジックの変更に怯えることがなくなった

時間の兼ね合いから、テスト対象は限定する必要がありましたが、依存関係の中心になるドメインオブジェクトやユースケース、広範囲で再利用されるユーティリティに関しては必須でテストを行うようにしました。これにより中核部分のコード品質が高くなり、重要な業務ロジック部分の実装に対しての不安が無くなりました。

テスト実装を必須とした箇所
  • ドメインオブジェクト
    対象:Entity, ValueObject, DomainService, CollectionObject, 振る舞いを持つEnum
    理由:ドメインの概念やルールを表し、業務ロジックを表現するための重要な部分であるため

  • ユースケース
    対象:Actions
    理由:ドメインオブジェクトの操作の組み合わせでユースケースやサービスを組み立てる層として重要であり、経年的に煩雑化しやすい部分であるため

  • 広範囲で再利用されるユーティリティ
    対象:Helpers, Packages, Traits
    理由:多くのクラスで呼び出される共通部品で、影響範囲が広いため

テストコードの存在により新規参入者の仕様理解が早くなった

プロジェクトを進める中でメンバーの増員がありましたが、テストコードの存在により、新規参入者が実装に入るための仕様の認識合わせは、補足程度で済むようになりました。

テストコードを読みやすく、意図を把握しやすくするために実施した取り組みの中から4つを紹介します。

テストのメソッド名とテストパターン名は日本語を使用する

メソッド名・テストパターン名を、慣れ親しんでいる日本語で表現することで、曖昧さを排除しつつテスト内容を一目で判断できるようにしました。

<?php

/**
 * @test
 * @dataProvider dp_条件に違反する値を設定してcreateすると、例外が発生する
 * @param string $attributeName
 * @param string $invalidValue
 * @return void
 */
public function 条件に違反する値を設定してcreateすると、例外が発生する(string $attributeName, string $invalidValue): void
{
    $this->createParameters[$attributeName] = $invalidValue;

    $this->expectException(DomainValidationException::class);

    EventDate::create(...$this->createParameters);
}

/**
 * @return array
 */
public function dp_条件に違反する値を設定してcreateすると、例外が発生する(): array
{
    return [
        '日時がYYYY/MM/DD hh:mm:ss形式以外' => ['date', '2000/01/01'],
    ];
}
テストのメソッド内ではGiven-When-Then構文を使用する

Given、When、Then の3つのブロックに分けて書くことで、どのような条件か、何を実行するのか、期待される結果は何かを明確にしました。

<?php

/**
* @test
* @return void
*/
public function createに値を渡すと、渡した値でインスタンスが生成される(): void
{
    // Given(前提条件)
    $id   = 'ID';
    $name = '氏名';

    // When(操作)
    $inquiry = Inquiry::create(
        id:   $id,
        name: $name,
    );

    // Then(期待する結果)
    $this->assertSame($id, $inquiry->id);
    $this->assertSame($name, $inquiry->name);
}
同種のパターン系のテストはData Providersを使用する

同じアサーションで複数パターンの値を検証したい場合にData Providersを使用することで、テストメソッドの記述量を減らし、想定されるパターンが一目で分かるようにしました。

<?php

    /**
     * @test
     * @dataProvider dp_条件に違反する値を設定してcreateすると、例外が発生する
     * @param string $attributeName
     * @param string $invalidValue
     * @return void
     */
    public function 条件に違反する値を設定してcreateすると、例外が発生する(string $attributeName, string $invalidValue): void
    {
        // Given(検証値のみ上書き)
        $this->parameters[$attributeName] = $invalidValue;

        // When
        $this->expectException(DomainValidationException::class);

        // Then
        Inquiry::create(...$this->parameters);
    }

    /**
     * @return array
     */
    public function dp_条件に違反する値を設定してcreateすると、例外が発生する(): array
    {
        return [
            '氏名が21文字以上' => ['name', 'テスト氏名テスト氏名テスト氏名テスト氏名テ'],
            '電話番号が12文字以上' => ['phoneNumber', '123456789012'],
            '電話番号の形式でない' => ['phoneNumber', 'phone111111'],
            '対応日時が日時の形式でない' => ['createdAt', '2022-02-03'],
        ];
    }
モックに対しスタブメソッドを定義する場合は、expectsまたはallowsを目的別に使い分ける

スタブメソッドの作成時は以下の基準を設けました。expectsとallowsの使用基準を明示的に分けることで、メソッド内で最もテストしたい事柄を明示的に示すようにしました。

メソッド 基準
expects - 定義するメソッドが必ず呼び出されていることを確認したい場合
- テストを行う上で重要な条件となるメソッドとして明示したい場合
allows - テストを通すためだけに戻り値を定義したい場合
- 必ず呼び出されるかを問わない場合

今後の展望

TDDに挑戦しようとして惜しくも断念しましたが、結果としてユニットテストの実装によりコード品質が向上したことで、今後の保守運用も容易になると確信しています。
その一方で、テスト実装とコードレビューにかなりの工数が掛かってしまったことは課題に感じています。今回得た知見を土台にしつつ、AIも活用することで、実装者とレビュワーの負担を減らしていきたいと考えています。

よりテストを強化していくにあたって、機能単位でのFeatureテスト、UI層におけるE2Eテストなどを行なった後、最終的にTDDに再挑戦することが目標です。これらの取り組みを実施して開発負荷を軽減することで、顧客満足度の向上に、より多くのリソースを割けるようにしていこうと思います!

スタイル・エッジでは、モダンな開発にも積極的にチャレンジしています!
ご興味ある方はぜひ採用サイト ↓をぜひご覧ください ♪
recruit.styleedge.co.jp

*1:以下のようなサイクルを回すことで「動作するきれいなコード」を目指すTDDの進め方。
1.次の目標を考える
2.その目標を示すテストを書く(テストファースト)
3.そのテストを実行して失敗させる(レッド)
4.目的のコードを書く
5.2で書いたテストを成功させる(グリーン)
6.テストが通る状態のままコードのリファクタリングを行う
7.1~6を繰り返す

【モダン開発 番外編】気が利くDTO!データオブジェクトライブラリ「Laravel-data」を導入してみた

generated by DALL-E3

はじめに

こんにちは。前回の記事ではコード標準化に勤しんでいたneueです。

連載中のモダン開発を行う際、自身はプロジェクトの本メンバーではないものの、
処理の共通化や骨組みの構築などを通して、開発支援に近い立場で参画していました。

モダン開発を実現するにあたり、設計思想の遵守に必要なロジックを愚直にコードで表現していると、早い段階で手続きの多重化やコード量の増加と向き合うことになるのではないかと思います。

その設計思想に寄り添って作られたフレームワークがあれば話は別ですが、実際のところは既存のフレームワークとの共存方法を探りつつ、フレームワークがもたらす利便性や開発体験を、ある程度損なわない仕組みを自前で用意することになるのではないでしょうか。

今回はモダン開発の番外編として裏側の一部を切り取り、データクラス(主にDTO)の実装に使用したLaravel-dataというライブラリについて、紹介いたします。

DTO(Data Transfer Object)とは

DTOとは、プログラミングにおけるデザインパターンの一種で、関連するデータがまとまっており、書き込み/読み取りが可能なオブジェクトのことを指します。
主に、プログラム間やレイヤー間のデータの受け渡しに用いられます。

DTOを介することで、以下のようなメリットを享受することができ、
レイヤードアーキテクチャに則った今回の設計でも活用しました。

  • 受け渡し先に必要なデータ構造にすることで、責任の範囲を明確化できる
  • メソッドのパラメーターをシンプルに保てることで、定義の重複や改修時の不備を避けられる
  • DTOの生成時点で型チェックやバリデーションを行うことができる
    • 利用側は必須・任意パラメーターの情報を前提とした実装にすることができる
  • (濫用は避けるべきではあるが)再利用が可能になる

Laravel-dataとは

Laravel-dataは、Spatieが提供する以下のような機能を持つデータクラスを作成するためのライブラリです。
DTOをはじめとしたデータクラスの実装にあたり、あると便利な機能が揃っている印象でした。

  • データオブジェクトを自動的にリソースに変換 (Laravel APIリソースなど)
  • データオブジェクトの遅延プロパティのみ後から変換
  • リクエストデータからデータオブジェクトを自動的に作成・検証
  • データオブジェクト内のプロパティのバリデーションルールを自動的に解決
  • 任意の型からデータオブジェクトを作成
  • データオブジェクトの作成時に自動的にバリデーションを実行
  • データオブジェクトからTypeScript定義を生成(フロントエンド利用向け)
  • データオブジェクトをEloquentモデルのプロパティとして保存

Introduction | laravel-data | Spatie

使用方法

導入はいたってシンプルで、基本的にはLaravel-dataが提供するDataの子クラスを作成するだけです。

<?php
use Spatie\LaravelData\Data;

class PostData extends Data
{
    public function __construct(
        public string $title,
        public string $body,
        public ?int $authorId,
    ) {}
}

作成した子クラスにアトリビュートを定義することで、値の検証や変換の機能を付与することができ、データオブジェクト生成用のメソッドを追加することで、読み手に優しく防御的なデータクラスが表現できます。

<?php
use Spatie\LaravelData\Attributes\Computed;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use App\Models\Author;
use App\Models\Post;

// 生成元オブジェクト上でスネークケースのプロパティ名をキャメルケースに変換
#[MapInputName(SnakeCaseMapper::class)]
class PostData extends Data
{
    // 計算済みプロパティはコンストラクターのプロパティに含めずに定義
    #[Computed]
    public ?string $authorName;

    public function __construct(
        // バリデーションルールを定義
        #[Min(20), Max(50)]
        public string $title,
        public string $body,
        public ?int $authorId,
    ) {
        // パラメーターに渡された値を元にした計算をコンストラクター内で行いプロパティの値を初期化
        $this->authorName = is_int($authorId) ? Author::first($authorId)?->name : null;
    }
    
    // Postインスタンスからのデータオブジェクト生成用メソッド
    // ※Eloquent Modelは標準で生成メソッドが用意されているため、互換性があれば定義不要
    public static function fromPost(Post $post): self {
        return new self($post->title, $post->body, $post->author?->id);
    }
}

実際にデータオブジェクトを生成する際は、fromメソッドを用いるのが一般的です。
他言語でいうところのオーバーロードに近い使用感で、引数の数や型を元に、実際に処理に使うメソッドが自動的に選択されます。

<?php
PostData::from(['title' => 'title', 'body' => 'body']);
PostData::from('{"title":"title","body":"title"}');
PostData::from(Post::first(1));

その他の機能についても、公式ドキュメントにてサンプルコード込みで簡潔に紹介されており、比較的学習コストは低いため、この記事を読んで気になった方はぜひ一度ご確認ください。

使用にあたって工夫したこと

基本的には標準機能を活用しつつも、実際の使用にあたってはいくつか検討や調整が必要になりました。

データ種別ごとの親クラスを作成

具象データクラスは、目的ごとの共通のふるまい(生成ロジック、変換に必要な処理など)を一元化するため、Dataを直接継承せず、目的別の抽象データクラスを経由させました。

<?php
abstract class BaseActionInputDto extends Data {
    // Actionへの入力用DTOで使用する共通のふるまいをメソッドとして定義
}

// 上記の抽象クラスを継承することで、Dataの提供する機能+目的別の追加機能が使用可能
class UserCreateActionInputDto extends BaseActionInputDto {}
class PostEditActionInputDto extends BaseActionInputDto {}

アトリビュートにまつわる注意点

Dataの作り上、アトリビュートは実行時のクラスに定義したものだけが読み取られます。
つまり、マッピングなどのルールが共通事項であっても抽象クラス側に書くことができません。
定義先が分散しない利点はありますが、個別の特性か共通の特性かが分かりにくくなるので、やや惜しさを感じます。

<?php
#[MapInputName(SnakeCaseMapper::class)] // 効かない
abstract class BaseActionInputDto extends Data {}

#[MapInputName(SnakeCaseMapper::class)] // 効く
class UserCreateActionInputDto extends BaseActionInputDto {}

共通の抽象クラス

余談にはなりますが、データクラスに限らず同種クラスの単位では共通の抽象クラスを経由させておくことで、静的解析やメタ的な判定がしやすくなるという副次的なメリットもあります。
その際は、phpatなどを用いて継承のルールが徹底される状態にしておくと信用度も上がります。

<?php
$isEntity = is_a($user, BaseEntity::class);

自動キャスト

Laravel-dataにはキャストという機能があり、入出力間で型を変換することが可能です。

主にプリミティブ値(文字列、数値など)として入力されたものを、DateTimeEnumなど扱いやすいオブジェクトに変換する目的で使用されることが多く、独自のキャストクラスを追加することもできます。

Enum

BackedEnum用のキャストクラスは標準で用意されていましたが、システム上ではPureEnumが多かったこともあり要件を満たせませんでした。

標準のキャストクラスを無効化したうえで、共通の親クラスであるUnitEnum用のキャストクラスを作成することで、値だけでなくインスタンス名からもEnumキャストできるようにしました。

配列の要素

配列における各要素の型は、PHPDoc上では明示できるものの、現在のPHPの型宣言では表現することができません。 ゆえに型宣言を元にしてキャストすることも難しいのですが、要素の型を情報として付与するアトリビュートを作成し、配列に自動適用するキャストクラスを作成することで実現しました。

<?php
private function __construct(
    // int配列にしたいとき
    #[ArrayOf('int')]
    public array $counts,
    // 特定のEnum配列にしたいとき
    #[ArrayOf(ClientType::class)]
    public array $clientTypes,
) {}

任意プロパティの省略

Laravel-dataには、オプショナルプロパティという機能があり、一部のプロパティが省略可能なデータオブジェクトを生成できます。

基本的にはユースケースの単位で専用のDTOを用意しているので常に必要なものではありませんが、例えば同一の更新処理であっても、一部のプロパティが操作したユーザーの権限によって送信されないケースなどで重宝します。

nullといった値に置き換えて判別する方法と比べて「そもそも情報を送信しない場合」と「送信はするが値が空の場合」の区別が可能になり、型宣言を見るだけでどのパターンが許容されているプロパティなのかを把握しやすくなります。

<?php
class UserDto extends BaseDto
{
    public function __construct(
        // 空の値が許容されるプロパティ
        public ?string $nullableProperty,
        // 未送信が許容されるが空の値は許可されないプロパティ
        public string|Optional $optionalProperty,
        // 未送信も空の値も許容されるプロパティ
        public string|Optional|null $optionalNullableProperty,
    ) {}
}

遅延格納型の計算値プロパティ

パラメーターで渡された値を元にした計算値プロパティは、コンストラクター内で初期化を行うことで実現可能です。

ただし、常に利用されない値など利用されるタイミングで初期化処理が走ればよいものも多かったため、抽象クラス側のマジックメソッド(__get, __isset)と、具象クラス側のアクセサーメソッド(getXxxxAttribute)を定義することで実現しました。

<?php
abstract class BaseDto extends Data
{
    public function __get(string $name): mixed
    {
        // $this->get{$name}Attributeメソッドの存在確認+実行結果の返却
    }
}

class UserDto extends BaseDto
{
    protected function getFullNameAttribute(): string
    {
        return $this->lastName . ' ' . $this->firstName;
    }
}

echo $user->fullName; // 'Yamada Taro'

※アクセサー用にgetXxxxAttributeといった名前のメソッド名を定義する方式は、Laravel8以前のEloquent Modelから着想を得ていますが、9以降は実装方式が変更されているので、今後見直す予定です。

主な使用箇所

続いて、実際のシステムではどのような場面で使用したのかをご紹介します。

ユーザー入力からユースケースへの情報受け渡し

DTOの使用例としては、ユーザーインターフェイス層とアプリケーション層間のデータ連携があります。
ユーザーが入力した情報を元に一度DTOを生成して、ユースケースに受け渡すようなシーンです。

基底のFormRequestに、内部に持つリクエストオブジェクトを用いてDTOを生成するメソッドを定義しておくことで、コントローラー側は入出力とオブジェクト間の中継役に徹することができます。

コントローラー上の流れ

  1. 対象画面用のFormRequestがDIされ、入力値のバリデーションが行われる
  2. バリデーションを通過した際に、リクエストオブジェクトから対象ユースケース用のDTOを生成
  3. 対象ユースケースの呼び出し時の引数としてDTOを渡す
<?php
class UpdateController extends BaseController
{
    public function __invoke(UpdateRequest $request, UpdateUserAction $updateUserAction): RedirectResponse
    {
        // DTO作成・ユースケース実行時などのエラーハンドリングは省略
        $updateUserAction($request->toData(UpdateUserActionInputDto::class));
        
        return redirect()->route('users.show', ['id' => $request->id]);
    }
}

ドメインオブジェクトの情報をビュー向けに加工

もう1つの使用例は、ドメインオブジェクトの情報をビューで扱う際に使用する独自のデータオブジェクトです。

クエリのユースケースにはドメインオブジェクト(エンティティなど)を返すものがありますが、対象のビューにとっては不要な情報を含んでいたり、外部オブジェクトへの参照がID形式などの情報の過不足に加え、連載でも触れられていた不都合な点があるため直接は使用できません。

ビューの責務としては渡されたデータを過度な加工なく表示する程度に留めておきたく、かといってコントローラー上で細かい加工処理まで受け持ってしまうと全体の流れの見通しが悪くなりがちです。

加工内容や具体的な方法については、いくつかのパターンに分類できることもあり、ビューで必要な情報・加工方式を定義するだけで希望形式のデータオブジェクトを返却するデータクラスを作成することになりました。

コントローラー上の流れ

  1. ユースケースを用いて対象のエンティティを取得
  2. エンティティ(と情報突合用の別オブジェクトのコレクション)からデータオブジェクトを生成
  3. view()の第2引数(配列)の一要素としてデータオブジェクトを渡す
<?php
class EditController extends BaseController
{
    public function __invoke(string $id, GetUserAction $getUserAction, GetAllDepartmentsAction $getAllDepartmentsAction): View
    {
        // 部署のエンティティコレクションを(再利用するのであらかじめ)取得
        $departments = $getAllDepartmentsAction();
        return view('pages.users.edit', [
            'user' => UserVoo::from(
                // 対象のエンティティ(ユースケース経由で取得)
                $getUserAction($id),
                // ユーザー内の所属部署がID参照のため、部署情報との突合用に部署のエンティティコレクションを渡す
                ['department' => $departments],
            ),
            // 部署のエンティティコレクションからデータオブジェクト配列に変換(collectionFromは独自実装)
            'departments' => DepartmentVoo::collectionFrom($departments),
        ]);
    }
}

データクラス上の処理

利用側をシンプルに保つ反面、裏側の処理としてはデータクラスの定義を走査して、対象のドメインオブジェクトの情報を加工しつつ当てはめたものをデータオブジェクトとして返す、泥臭い作りになりました。

なお、突合時に必要な外部コレクションの取得を内部に持たせることも技術的には可能でしたが、ユーティリティ的概念からユースケースへの暗黙的な依存が生まれるリスクも感じたため、今回はコントローラー上で取得したものを引数として渡す実装にしました。

  • データの絞り込み
    • 入力されたエンティティ・値オブジェクトから、データクラスに定義したプロパティの情報のみを取得
  • データの構造化
  • データの加工
  • データの拡張
    • 定義での表現が困難な項目や計算値は、アクセサーメソッドを定義することで実現

おわりに

ライブラリの力も借りつつ、データの挙動を制御する処理を基底側に閉じ込めることで、実際の開発時に多く目に触れるであろう具象クラスは、概念の所有する情報が際立つオブジェクトに近づけることができました。

もちろん良いことばかりではなく、与えられている責務がデータクラスの範疇を超えているという見方や、ブラックボックス化やパフォーマンスなどの懸念がないわけではありません。

設計思想であらかじめ定めた遵守すべきラインの中で、本来の目的・運用時のコスト・開発者の属性・準備期間なども加味して今回の最適解を導き出したつもりですが、状況が変化したタイミングでより良い作りにアップデートすること(できるような作りを意識すること)が前提になるのではないかと思います。

長くなりましたが、スタイル・エッジでは、こういった縁の下のパワータイプなエンジニアもガッツリ募集中です!一緒に足場を組み立ててみたいと思いましたら、ぜひ採用サイトをご覧ください。 recruit.styleedge.co.jp

Google Workspace ユーザー会に登壇しました!

はじめに

皆さんこんにちは。スタイル・エッジのはるです🌞

とあるご縁をいただき、2023年10月27日、渋谷にあるGoogle Japan のオフィスにて開催されたUSEN Smart Works社主催の Google Workspace ユーザー会に登壇してきました!
今回はユーザー会に登壇するに至った背景をはじめ、私たちが当日語ったこと、そして感じたことを思いのままに書いていきたいと思います。

Google Workspace ユーザー会で登壇することになった経緯

ユーザー会とは

そもそもユーザー会とは、ある企業の製品やサービスを利用するユーザー同士や、サービスを提供する企業とユーザーが情報交換などを行う場のことを指します。

今回は Google Workspace を提供する企業・利用するユーザー企業が集まって開催されました。
プログラムの1つに Google Workspace 活用企業事例の枠があり、今回は弊社が話をする機会をいただきました。

Google Workspace の積極活用

弊社で Google Workspace の利用を開始したのは2020年から。
Google Workspace Enterprise Plus という最上位のプランを利用しています。クラウド環境の拡充、セキュリティ強化を図り活用を開始しました。

Google ドライブGoogle ドキュメントGoogle カレンダー ・ Gmail など豊富なサービスが利用できますが、 最近特に力を入れているのがノーコードツールである AppSheet の活用です。

AppSheet とは

AppSheet は Google Cloud のノーコードツールであり、有料版の Google Workspace ライセンスを保有していれば、AppSheet Core(有料版)を自由に使用できます。 (2023年12月現在)
Google Cloud のノーコードツールということもあり、 Google Workspace との連携も容易な点が強みです。
有料版のライセンスも使用でき拡張性が高い点も考慮すると、
Google Workspace ユーザーにとって AppSheet は数多あるノーコードツールの中でも有力な候補になるのではないでしょうか。

弊社では今年に入ってから力を入れて AppSheet を活用し始めました。

ITガバナンスの強化

AppSheet の活用と併せて、弊社では Google Workspace をITガバナンスに活用しようと努めています。
というのも、弊社で契約している Enterprise Plus は何といってもセキュリティ機能が売り!
活用しない手はないですね👏

セキュリティ機能が充実の Enterprise エディション

Google Workspace のプランは様々ありますが、エディションという括りで捉えると違いが分かりやすいです。
公式では以下のように各エディションを説明しています。

Google Workspace の各エディションの比較

Google Workspace には、個人、中小企業、大規模組織、学校向けのサブスクリプション オプションがあります。

  • Business - ユーザー数が 1~300 人のビジネス向けの生産性向上スイート
  • Education - 所定の要件を満たす教育機関向けの割引されたスイート
  • Enterprise - 無制限のユーザーに対する優れたセキュリティと高度な制御が追加で利用可能
  • Essentials - Gmail を使用しない生産性向上ツールとコラボレーション ツール
    support.google.com

公式の説明からも、Enterprise エディションはセキュリティ・制御機能に力を入れていることが一目瞭然です!
弊社がITガバナンスに【 Google Workspace Enterprise Plus 】を生かしたい所以がここにあります👍

具体的には「DLP」や「セキュリティセンター」といった機能に注目していますが、
詳細な話はまた別のブログ記事で書けたら面白いかも?と思っています!

ユーザー会で話したこと

前置きが長くなってしまいましたが、、 いよいよユーザー会に登壇した時の話です!

ザックリと今回話したトピックを紹介します。
今回の登壇に向けて、弊社のデザイナーと協力して登壇資料を作成しました!
エンジニア×デザイナーという職域の枠を越えたコラボレーションができるのは強みだなぁとしみじみと感じました...👏 (おかげで資料に対する反響も沢山いただきました🌟)

というわけで、当日使用したスライドも交えてお伝えします!
ざっくりと、以下3つの軸で話をしました!

Google ドライブ 利用ルールの整備

Google Workspace の中でも利用者が特に多いであろう、 Google ドライブ
とっても便利なサービスである一方、データのガバナンスをどのように行っていけばよいのか悩むケースも多いのではないでしょうか?
ユーザーにとっての使いやすさ、データ管理のバランスを考慮した弊社の運用ルールを紹介しました。

AppSheetの活用

先述したAppSheetの活用について紹介しました。
Google Workspaceの力を更に引き出す」をコンセプトに、AppSheetとGoogle Workspaceをセットで活用するメリットをメインに話しました。
※Style Workspace という表記は弊社内部で呼称しているサービス名です。
 Google公式の製品名ではないため悪しからず...笑

ITガバナンスの強化

最後にセキュリティ機能の活用。
ここはGoogle Workspaceの管理者が最も手を出しづらい領域なのではないでしょうか...?
非常に強力な機能が備えられている一方でどのように使ったらいいかわからない... 私たちもそうでした。
今回は、「弊社のセキュリティルールをシステマチックに制御する」という観点で行っている施策について話しました。

ユーザー会に参加して感じたこと

Google Workspace ユーザー会での登壇は弊社にとっても非常に学びのある機会となりました。

自社オリジナルの視点に気が付けた

日々目の前の業務に必死になっていると、悲しくも自分の立ち位置が客観的に見えなくなるものです...
今回はユーザー会登壇にあたり、何を話したらユーザー会に参加される皆さんに価値を提供できるのか色々と考えました。

その中で、「セキュリティという足場固め」と「先端技術活用による攻め」の両立といった私たちが大切にしているスタンスや、それをどうやってツールを通して実現しようとしているのか振り返るきっかけになりました。

Google Workspace・AppSheet を活用した攻めるために守る、守るために攻める姿勢を引き続き貫いていきます。

他社でも似た悩みを抱えており、相談できる場が貴重だと感じた

当日は他社様のシステム管理者の方も沢山参加されており、皆さんが抱えていらっしゃる悩みを交換する場もありました。
私たちが悩んでいることはみんなも悩んでいるんだと気が付き、仲間は沢山いるなという気持ちになりました笑

と同時に、私たちが工夫している運用を発信していくことは多くの悩みを解決することに繋がるという実感も得ました。
実際に、 Google ドライブ を組織として管理する上で工夫している弊社の運用が勉強になったとの声もいただき嬉しく思いました。

既存ツールの活用もエンジニアリングであると感じた

ユーザー会登壇にあたり弊社の Google Workspace の運用を振り返り、当日は Google Cloud から直接 Google Workspace の活用方法をレクチャーしていただく機会を得ました。
その中で、新規でシステムを開発するばかりでなく状況に応じて既存のツールを使いこなすのもエンジニアリングであるなぁと感じました。

自力でやらなくてもいいことが増えている昨今、システムを分かったうえで既存サービスに上手く任せていく。
そんな力を引き続き養っていきたいと思います。

最後に

今後も自社オリジナルの運用を強化し、サービスの活用を推進していきます。
面白い施策はまた発信できればと思いますので乞うご期待です!笑

SaaS乱立時代の波を一緒に乗りこなさんとする方、攻めのバックオフィスを実現していきたい!という熱い想いのある方、そんな仲間をお待ちしています! recruit.styleedge.co.jp

【モダン開発 #2】実践!DDD × レイヤードアーキテクチャ in Laravel

はじめに

こんにちは!スタイル・エッジの SHISO です。 弊社ではある業務システムの開発プロジェクトにて、ドメイン駆動設計(DDD)に挑戦しました。
※ 詳しい経緯についてはこちら

今回は、Laravelを利用するという条件の下、DDDとレイヤードアーキテクチャを実現していくにあたり、具体的にどのような設計方針で進めたのか、またどのように実装に落とし込んだのかなど、紹介していきたいと思います。

目次

アーキテクチャ

DIP(依存性逆転の原則)を用いたレイヤードアーキテクチャを採用しました。

レイヤードアーキテクチャ図
レイヤードアーキテクチャ

Laravelフレームワークの一部概念については、意図的にレイヤーを逸脱することを許容しつつも、 原則として、依存関係は上位から下位のみ許可する方針としています。

ディレクトリ構成

※本記事で言及しない部分は省略しています。

app/
├─┬ Application/ (アプリケーション層)
│ ├── Actions/ (ユースケース)
│ └── Dtos/
├─┬  Domains/ (ドメイン層)
│ └─┬ 境界づけれられたコンテキスト/
│   └── 集約/
├─┬ Http/ (UI層)
│ ├── Controllers/
│ ├── Requests/
│ └── Voos/ (ビュー用オブジェクト)
└─┬ Infrastructure/ (インフラ層)
  ├── Models/
  ├── QueryServices/
  └── Repositories/

各層の責務や実装方針、特徴

具体的に各層に持たせた責務と、実装方針をピックアップして紹介します。

Domain層

ビジネスの概念と、ビジネスが置かれた状況に関する情報、およびビジネスルールを表す層です。
主に下記ドメインオブジェクトのパターンで分類し表現しました。

  • エンティティ
  • 値オブジェクト
  • 区分オブジェクト
  • コレクションオブジェクト
  • ドメインサービス
  • 仕様
  • ファクトリ

Domain層は、境界づけられたコンテキストや関連性によってディレクトリを切っていきます。最小単位は集約ごとです。

エンティティ・値オブジェクト生成時のデータ整合性担保のタイミング

エンティティ・値オブジェクトの生成は、基本的に各オブジェクト内のファクトリメソッドで行うようにしました。
新規生成用ファクトリメソッドでは、データ整合性担保のバリデーションを行った上でオブジェクトを生成します。
DBからの再構成用ファクトリメソッドでは、パフォーマンスも考慮し、データの整合性は担保されている前提で、バリデーションは行わず生成しています。

また、ローカルエンティティや値オブジェクトを保持するエンティティ(集約)の生成では、ルートエンティティのcreate処理にて、集約内のデータ整合性担保を行いました。

<?php

// 受付エンティティ
class ReceptionEntity
{
    private function __construct(
        public readonly ?string $id,
        public readonly string $receptionAt, // 受付日時
        public readonly ReceptionType $receptionType, // 受付種別
        public readonly string $name, // 氏名
        public readonly ?Tel $tel, // 電話番号
    ){}

    // 新規生成用ファクトリメソッド
    public static function create(
        string $receptionAt,
        ReceptionType $receptionType,
        string $name,
        ?string $tel,
    ): ReceptionEntity
    {
        // データ整合性担保のバリデーションを行った上でオブジェクトを生成
        validator(
            ['receptionAt' => $receptionAt, 'name' => $name],
            self::rules()
        )->validate();

        // 集約内のデータ整合性担保はルートエンティティのcreate処理で行う
        if ($receptionType === ReceptionType::New && is_null($tel)) {
            throw new \Exception('新規受付の場合、電話番号は必須です。');
        }
        $tel = isset($tel) ? new Tel($tel) : $tel;

        return new self(null, $receptionAt, $receptionType, $name, $tel);
    }

    private static function rules(): array
    {
        return [
            'receptionAt' => ['date'],
            'name'        => ['max:10'],
        ];
    }

    // 再構成用ファクトリメソッド
    public static function reconstruct(
        string $id,
        string $receptionAt,
        ReceptionType $receptionType,
        string $name,
        ?Tel $tel,
    ): ReceptionEntity
    {
        // データは整合性担保されている前提で、バリデーションは行わない
        return new self($id, $receptionAt, $receptionType, $name, $tel);
    }
}

// 受付種別
enum ReceptionType
{
    case New; // 新規
    case Old; // 既存
}

ただ、集約内の情報だけでは生成ができない・データ整合性担保ができない場合は、ファクトリオブジェクトを別途作成し、 そこでエンティティの生成を行っています。

<?php

// 受付エンティティのファクトリ
class ReceptionEntityFactory
{
    // 集約内の情報だけでは生成ができない、またデータ整合性担保ができない場合は、ファクトリオブジェクトで生成
    public function create($name, ...): ReceptionEntity
    {
        // 例)同じ名前の受付が存在する場合、「既存」と判定
        $receptionType = isset($receptionEntityRepository->findByName($name)) ? ReceptionType::Old : ReceptionType::New;

        return ReceptionEntity::create($name, $receptionType, ...);
    }
}

エンティティ識別子はDBのAutoIncrementによる自動採番で生成

本来であれば、識別子はエンティティ生成時に採番した方が一意であることを担保できますが、今回はDBのAutoIncrementより自動採番で遅延生成することにしました。
理由としては、DBMSを今のところ変更する予定がないことと、一番は普段から馴染みのあるORM(Eloquentモデル)を使った時の挙動と近く、実装コストが格段に低くなるためです。
ただ、今後アプリケーション側で識別子を早期生成する場面は考えられるため、識別子の型はintegerではなくstringで定義しました。

バリデーションはLaravelのValidatorファサードを利用

今回開発した業務システムでは、管理する情報数や永続化において担保する必要のあるルールが多く、ドメインオブジェクトの属性ごとにバリデーション処理を記載すると、クラスが肥大化してしまう問題が起きました。

<?php

// before
class UserEntity
{
    private function __construct(
        public readonly ?string $id,
        public readonly string $name,
    ){}

    public static function create(string $name): UserEntity
    {
        // 下記のようなバリデーション処理がたくさんでき、クラスが肥大化
        if (mb_strlen($name) <= 10) {
            throw new \Exception('名前は10文字以下でしか登録できません。');
        }

        return new self(id: null, name: $name);
    }
}

そのため、属性ごとのバリデーションルールについては、LaravelのValidatorファサードを利用し、よりシンプルに記述できるようにしました。

<?php

// after(一部省略)
    public static function create(string $name): UserEntity
    {
        // LaravelのValidatorファサードを利用
        validator(['name' => $name], self::rules())->validate();
        return new self(id: null, name: $name);
    }

    private static function rules(): array
    {
        return ['name' => ['max:10']];
    }

※ 実際は基底クラスなどを作成し、より実装効率を上げる工夫をしています。

Application層

アプリケーションが提供するユースケースやサービスを、ドメインオブジェクトの操作の組み合わせで実現する層です。

パラメータが多い場合はDTOクラスを利用

ユースケースクラスの実行メソッドにて、パラメータが多い場合には、データ受け渡し専用のクラスとしてDTOを利用しました。
これにより、可読性や保守性が向上し、連想配列としてそのまま受け渡す場合に比べ、型により安全性が担保されました。
また、特定の条件ではありますが、ユースケースクラスから別ユースケースクラス呼び出し時にDTOを使い回すことで実装効率が向上する場面もありました。

DTOクラスについては、その他にも開発効率を上げるために様々な工夫をしましたので、 別の連載記事として紹介します。

UserInterface層

アプリケーション外部との入出力操作(データ変換含む)を行う層です。
主にコントローラやフォームリクエスト、ビューテンプレート(Blade)が当てはまります。

ドメインオブジェクトはビュー用のオブジェクトに変換

ビュー(Blade)を作成する際、アプリケーション層から取得したドメインオブジェクトの情報を使用する場面は多々あります。
その際、ドメインオブジェクトをそのままビューへ渡すと、実装効率は上がる反面、下記デメリットも発生します。

  • 予期せぬプロパティ操作や情報更新がされる恐れ
  • ドメインオブジェクト・集約の構造を意識したプロパティアクセスの考慮により実装効率が低下
  • 判定処理やIDとリストの照合処理など、表示に関わるコード以外の実装がビュー上に散見し可読性が低下

そのため、今回はドメインオブジェクトをそのまま渡さず、最小限の記述で必要な情報のみを参照できるように、ビュー用のオブジェクトに変換して渡すようにしました。

Infrastructure層

RDB等の具体的な外部リソース技術を使用し、ドメインオブジェクト(集約)の永続化・取得を行う層です。
主にリポジトリやクエリサービスの実装クラス、Eloquentモデルなどが当てはまります。

リポジトリパターンを採用

ドメインオブジェクトの永続化・取得にはリポジトリパターンを採用しました。
理由としては、抽象リポジトリに依存させ、容易にデータアクセス処理を書けなくすることで、ビジネスロジックがデータアクセス処理に漏れることを抑制するためです。
また、データアクセス処理の隠蔽や下位レイヤのテストが容易になるといった効果も期待できます。

集約内のローカルエンティティの更新処理は2通り

エンティティによっては、ネストしたエンティティ(ローカルエンティティ)を持つものもあります。
例えば、顧客管理システムにおいて、注文エンティティ内に注文アイテムエンティティを包含し、注文詳細情報を階層的に表現する場合、注文アイテムエンティティがローカルエンティティに当たります。

集約内のオブジェクトは、データ整合性担保のためにも、必ず集約単位で更新する必要があるので、ローカルエンティティはルートエンティティと同じタイミングで永続化処理が実行されます。
ただ、ルートエンティティとローカルエンティティは別テーブルで管理されることが多いため、ローカルエンティティの更新方法として、下記2通りの方法が候補に挙がります。

  • delete/insert
  • 一意な識別子による差分更新

差分更新の場合、selectで存在確認してからupdateかinsert、何もしないなど、いくつか分岐処理が必要になり、実装が複雑になるため、基本的にはdelete/insertにて実装しました。

<?php

// delete/insert  
class Repository
{
    public function updateOrCreate(RootEntity $rootEntity): void
    {
        $rootEntityEloquentModel = RootEntityEloquentModel::updateOrCreate(
            ['id' => $rootEntity->id, ...],
        );

        // ローカルエンティティの永続化
        $rootEntityEloquentModel->localEntityEloquentModel()->delete();
        LocalEntityEloquentModel::create(['root_entity_id' => $rootEntityEloquentModel->id, ...]);
    }
}

delete/insertでも集約内のオブジェクトは必ず集約単位で更新されるため、証跡データが必要な場合、ルートエンティティから取得できます。
しかし、厳密にローカルエンティティごとに更新日時や更新者など証跡データが必要な場合では、差分更新にて実装しました。

<?php

// 一意な識別子による差分更新  
class Repository
{
    public function updateOrCreate(RootEntity $rootEntity): void
    {
        $rootEntityEloquentModel = RootEntityEloquentModel::updateOrCreate(
            ['id' => $rootEntity->id, ...],
        );

        // ローカルエンティティの永続化
        if (isset($rootEntity->localEntity)) {
            // updateOrCreateメソッド内で更にデータが存在するかの分岐処理が走る
            LocalEntityEloquentModel::updateOrCreate([
                'id'             => $rootEntity->localEntity->id,
                'root_entity_id' => $rootEntityEloquentModel->id,
                ...]);
        } else {
            $rootEntityEloquentModel->localEntityEloquentModel()->delete();
        }
    }
}

複雑なデータアクセス処理にはクエリサービスを利用

リポジトリパターンを採用することでメンテナンス性は良くなりましたが、複数集約を取得したり、複雑な条件の検索処理などはパフォーマンスが悪化しました。
そのため、集約を跨ぐ取得や検索処理など特定の処理については、CQRSパターンを一部参考にし、リポジトリとは別にクエリサービス*1というクラスに切り出しました。
ただこのクエリサービスの利用については、ビジネスロジックの重複や設計指針の崩壊などが発生し得るため、本当に利用しなければならない理由がある時のみ利用しています。

Eloquentモデルはデータベース操作でのみ利用

基本的にEloquentモデルとドメインオブジェクト(主にエンティティ)は1対1になるため、設計当初はmpywさんの記事を参考にしつつ、Eloquentモデルでドメインオブジェクトを表現しようとしていました。
しかし、Eloquentモデルは各属性ごとに更新可能であるため、属性間のデータ整合性を担保することが難しかったり、またドメインロジックやビュー用処理、データベース設定などがEloquentモデル内に混在してわかりづらい、かつファットになりやすかったため、今回Eloquentモデルはリポジトリ・クエリサービスからのデータベース操作でのみ利用しました。

その他

バリデーション実装方針

各層の責務範囲でバリデーションを行う

バリデーションは、UI層・アプリケーション層・ドメイン層の各層にて、それぞれの責務範囲内で行いました。

まずUI層のフォームリクエストでは、必須チェックや入力形式チェックなど、アプリケーション層に渡すための最低限のバリデーションのみを行います。
次にアプリケーション層では、ユースケースの実現に必要なドメインオブジェクトを利用しバリデーションを行います。
(あくまでビジネスロジックドメインオブジェクトに記載し、アプリケーション層はそれらを使って返ってきた結果をみて例外を投げています。)
最後にドメイン層にて、エンティティや値オブジェクト生成のためのデータ整合性担保のバリデーションを行います。

これにより、ドメイン層以外にドメインルールが漏れるなどの逆方向への依存や、コードが重複することなどの事象を回避することができました。

バリデーションエラーは可能な限りまとめて返す

エラー内容はバリデーションに引っかかる度に1つずつ返すより、可能な限りまとめて返した方が、一般的にはユーザー体験が向上します。
アプリケーション層やドメイン層も、Laravelのフォームリクエストに倣って、一通りのバリデーションを実行した後にエラーをまとめて返却するように実装しました。

終わりに

今回は、連載2回目ということで、Laravelを利用するという条件の下、DDDとレイヤードアーキテクチャを実現していくにあたり、具体的にどのように落とし込んでいったのか、特に悩んだ部分についてまとめました。

DDDはいざ実践するとなると、大枠は各文献を参考にしながら進めることはできましたが、より詳細な部分については、自分たちで情報収集・試行錯誤・取捨選択して進める必要があり、とても苦労しました。
そのため、ぜひ一例として、これからDDDを始める方や今まさに実践している方の参考になれば幸いです。


このようにスタイル・エッジでは、DDDなどのモダンな開発にも積極的にチャレンジできる環境です!
ご興味ある方はぜひ採用サイト ↓をぜひご覧ください ♪
recruit.styleedge.co.jp

次回は「【モダン開発 #3】TDDに挑戦してみた!」を投稿します。お楽しみに~

『GitLab に学ぶ 世界最先端のリモート組織』に学ぶ、リモートチームにおける心理的安全性の醸成

はじめに

こんにちは、しおです。
前回は MySQL のコードリーディングしてみる、という記事を書きました。 techblog.styleedge.co.jp

今回はコードリーディングの続きではなく、最近読んだ面白い本の一部を紹介してみようと思います。

何の本か

今回読んだのは、『GitLab に学ぶ 世界最先端のリモート組織』という本です。 www.shoeisha.co.jp

世界 67 カ国以上に 2,000 名以上の従業員を抱える GitLab 社が、リモートワークにおける方法論やカルチャーの醸成方法をまとめて無料で公開している GitLab Handbook を読み解いた本です。
GitLab Handbook は全部で4,000ページ弱ほどあり、GitLab 社で業務を行ううえで必要な知識がほぼすべて網羅されているとのこと。例えば、コミュニケーション方法から報酬の決定方法や評価方法までもがドキュメントにまとめられているようです。

『GitLab に学ぶ 世界最先端のリモート組織』の著者は GitLab の社員ということではないようですが、この GitLab Handbook を翻訳して読み解き実践することで、自社をオフィスを持たない完全フルリモート企業へと転換させました。そこでの経験を踏まえて GitLab Handbook のエッセンスを抽出したのが本書です。

本書には『心理的安全性の醸成』という独立した章が存在します。
今期の個人目標として『自分が所属するチームの心理的安全性を高める』という目標を立てていたこともあり、今回はこちらの章を中心に心理的安全性に関連した記述を拾い読みしてみました。

GitLab についておさらい

GitLab は、2011年にウクライナにてディミトリー・ザポロゼツ氏がスタートさせたプロジェクトです。

よく似たサービスに GitHub というものがありますが、GitHub と GitLab は思想が異なります。

GitHubSocial Coding を重視しており(4年前までプライベートリポジトリの作成が有料だったことからもその点がうかがえます)、Social Coding という言葉からも分かるように、GitHub にはエンジニアコミュニティのレベルを高めるという目的があり、特にOSS開発の領域で活発に利用されています。

GitLab は プロジェクト管理 を中心とした考え方で、データの秘匿性を重視するなど GitHub とは異なる思想の上で運用されており、プライベートリポジトリを無料で作成したりオンプレのサーバーを使って自社専用の環境を構築したりといったことが初期の段階から出来ました。

techblog.styleedge.co.jp なお、過去の記事にもあるように、スタイル・エッジ システム事業部では GitLab を活用してプログラムのバージョン管理を行っています。

心理的安全性について

心理的安全性とは、エイミー・C・エドモンドソン氏が定義した言葉で、「チームにおいて、『他のメンバーが自分の発言を拒絶したり、罰をあたえたりしない』という確信を持っている状態」であり、「信頼と尊敬が混ざり合う心理的に安全な文化」を指します。

心理的安全性が高い = ヌルい環境 ではない

心理的安全性の高いチームと聞くと、単純に「メンバーの仲が良くてどんなことでも言いあえるチーム」を(自分含め)想像してしまいがちです。
なかには、そこからさらに「なれ合い」「モチベーションが低い」「現状維持」といったマイナスなワードを連想してしまう方もいらっしゃるかもしれません。

前述のような状態は、下図において「快適ゾーン にいる」と言えます。しかし、チームや事業としての高い目標や基準と心理的安全性の高さは両立が可能です。

私が現在所属しているチームは、心理的安全性が高いと評価してくださる方が多いです。また私自身もそう思っており、今後も心理的安全性が高い状態をキープしたいと考えているのですが、ここで更に上を目指そうと思ったときに目標になるのが、学習ゾーン です。

メンバーそれぞれが学習ゾーンにいるチームは、気持ちよくチームとして団結して困難な仕事を成し遂げられると『恐れのない組織――「心理的安全性」が学習・イノベーション・成長をもたらす』にて述べられています。

これ以上は記事の本旨から外れてしまうので詳しく紹介しませんが、心理的安全性については下記の本にて詳しく説明されているので、興味がある方はぜひ読んでみてください。

恐れのない組織――「心理的安全性」が学習・イノベーション・成長をもたらすeijipress.co.jp

GitLab 社における心理的安全性の醸成方法

さてようやく本題です。
GitLab 社において大事にされている『職場で心理的安全性を確立する 7 つの考え』を紹介したあとに、心理的安全性を醸成するために行われている具体的な手法を2つほどピックアップして紹介していきます。

職場で心理的安全性を確立する 7 つの考え

本書で紹介されている、心理的安全性を生み出すために GitLab 社において大事にされている考えは、下記の通りです。

  1. 黄金律を破る
  2. 好奇心を歓迎する
  3. 健全なコンフリクトを推進する
  4. 従業員に発言権を与える
  5. 信頼を獲得し、拡大する
  6. 効率だけでなく有効性を促進する
  7. 創造性について別の考え方をする

ここでそれぞれの考えについて詳細に解説することはしませんが、ピックアップして解説していきます。

黄金律を破る

アメリカでは、『自分がしてほしい行為を、他人に対して実施せよ』という黄金律があります。
心理的安全性を高めるには、『他人がしてほしいと思う行為を、他人に対して実施せよ』という行動規範が大切とされています。

blog.jostle.me

上記の GitLab Handbook からリンクされている心理的安全性を高めるための方法を紹介する記事によれば、「”自分が” こうしてほしいと思うから」ではなく、チームメンバーに対してどのようなコミュニケーションのスタイル、フィードバックの種類を好むのか希望を探り、その通りに実施するということです。ここでは自分視点ではなく、相手にどうしてほしいのかを主軸に考えています。
他人が何を望んでいるのか、どのように扱われることを望んでいるのか、ということを事前に知っておき、自分基準に依らないようなスタンスを取ることで心理的安全性に貢献をもたらします。

健全なコンフリクトを推進する

チームの雰囲気を悪くしないために衝突を回避するのではなく、必要なコンフリクト(衝突)は積極的にするべき、という考えです。
ただしその際には「コト」と「人」は分けるよう気を付け、決して人格を否定することの無いように努めます。

「同意しない、コミットする、同意しない」

レビュワー・レビュイー双方に、「意見や成果物を批判されても、人格まで批判しているわけではない」という認識を持つことが大事です。
意見や成果物は客観的な視点で公正に判断されるべきであり、それは個人の人格とは別の次元で議論しなくてはなりません。 handbook.gitlab.com

そのために、GitLab では最終的に DRI (Directoly Responsible Individuals) が結論を出します。 各自が意見を述べたうえで DRI が判断し、ひとたび結論が決まればメンバー全員がその決定を尊重してコミットすることが求められます。 handbook.gitlab.com

心理的安全性を維持しながらフィードバックする

パフォーマンスを改善するためにフィードバックをするのですが、ネガティブなフィードバックをすると人間はストレスを感じてしまいます。
そこで、GitLab ではフィードバックの手法として SBI モデルを使用しています。 handbook.gitlab.com

SBI モデルでは、簡単に言うと「状況(Situation)」「振る舞い(Befavior)」「影響(Impact)」を明確にしてフィードバックをします。
例えば「昨日の進捗定例会議でメンバーが発表している最中に(Situation)、部長が腕を組んで出席者を威圧していた(Behavior)。この行動が会議の参加者に不要なプレッシャーをかけ、自由な意見や提案を出しにくくさせる(Impact)可能性があるのでやめてほしい。」といった具合です。
SBI を明確に特定することで、 客観的な事実に基づくフィードバックになります。

おわりに

ということで、『GitLab に学ぶ 世界最先端のリモート組織』という本を読んで勉強になったことをつらつらと書いてみました。
GitLab 社はフルリモートの職場ということで、弊社に完全に適用できるものばかりではありませんが、SBI モデルを用いたフィードバックや黄金律を破るという心構え等、個人単位でも実施できそうな取り組みはぜひ積極的に取り入れてみたいと考えています。
本家の GitLab Handbook にも俄然興味が湧いてきたので、『GitLab に学ぶ 世界最先端のリモート組織』をお供にしてのんびり読み進めていきたいと思います!


スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。 もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください! recruit.styleedge.co.jp

【モダン開発 #1】「ドメインモデリング」こんな感じでやりました

はじめに

こんにちは!スタイル・エッジの SHISO です。
弊社ではある業務システムの開発プロジェクトにて、ドメイン駆動設計(DDD)に挑戦しました。
※ 詳しい経緯についてはこちら

今回は、DDDの中でも肝になってくる「ドメインモデリング」について、実際にプロジェクトで取り組んだ内容を紹介していきたいと思います。

ドメインモデリングとは

簡単に言うと「ドメインモデルを作成すること」ですが、先人達の著書*1より整理すると、下記のような定義であると言えます。

  • ソフトウェアを適用する領域から、問題解決のためにソフトウェアに落とし込む要素を取捨選択すること
  • システムにとって何の情報が必要かについて、事象あるいは概念を抽象化すること

実際に行ったドメインモデリングの流れ

大まかには下記の流れでドメインモデリングを実施していきました。

  1. 業務フロー等から「ヒト・モノ・コト」に該当するモデルをとにかく抽出
  2. システムが対象とする業務についての書籍や記事、または顧客ヒアリング等で業務知識を深めモデルを抽出
  3. 境界づけられたコンテキストを分割
  4. エンティティ、値オブジェクト、区分オブジェクト、集約を識別
  5. 仕様、ドメインサービス、ファクトリを識別
  6. ルールや属性値を元にモデルをさらに識別

あくまで大まかな流れなので、実際には行ったり来たりしてモデルを洗練することで、実用的な形へと蒸留していきました。

実際に作成したドメインモデル図の例

とにかく抽出する段階ではざっくりとしたモデル図で行いますが、最終的には主に下記のようなフォーマットでドメインモデル図を作成しました。

ドメインモデル図のフォーマット
ドメインモデル図のフォーマット
具体例として、実際に上記フォーマットでモデリングした図は以下の通りです。
※ 内容はあくまで例です。
ドメインモデル図のサンプル
ドメインモデル図のサンプル

ドメインモデル図を見れば、開発者がシステム全体の内容を把握できるようにしつつ、さらにはシステム開発のコア部分のレビューや認識合わせがこの図1つで集約できるように、戦術的設計パターンや属性ごとの型ルールまで記載するようにしました。
この方式により、開発着手前までに大方のフィードバックが可能になったため、開発着手からデプロイの中で大きな手戻りが起きることは少なくなりました。

ドメインモデリング実践で学んだこと

基本的には先人の方々の情報を参考にしつつ、ドメインモデリングに取り組みました。
ただ、実際にやってみると、参考となる情報がないことやうまくいかないこと、改めて強く意識したことなどがありましたので、いくつか紹介します。

モデリングはとにかく「ヒト・モノ・コト」に注目

業務の関心事を、ヒト/モノ/コトに3つに分類する。
 ・ヒト:個人、企業、担当者など
 ・モノ:商品、サービス、店舗、場所、権利、義務など
 ・コト:予約、注文、支払、出荷、キャンセルなど

コトに注目すると全体の関係を整理しやすい
 ・コトはヒトとモノとの関係として出現する(だれの何についての行動か)
 ・コトは時間軸に沿って明確な前後関係を持つ

コトは業務ルールの宝庫

引用元:現場で役立つシステム設計の原則

「ヒト・モノ・コト」に注目すると、大半のドメインの概念を抽象化できます(洗練されているかは別)。
また「コト」に関しては、積極的に抽象化する必要がある暗黙的な概念(ルール/仕様/イベントなど)が見つけやすくなります。

モデリングにはコード設計の観点が必要

優秀なドメインモデラは、経理担当者と一緒にホワイトボードを使うこともできれば、プログラマと一緒にJavaを書くこともできる。概念が実装から切り離せない理由の一端には、実装上の問題を考慮せずには役にたつ概念モデルを構築することができない、ということもある。しかし、概念と実装を一緒に考える一番の理由は、ドメインモデルの最大の価値がユビキタス言語を提供し、ドメインエキスパートと技術者を結びつけることにある、ということだ。

引用元:エリック・エヴァンスのドメイン駆動設計
ドメインモデリングしてソフトウェアに落とし込む時には、どのモデルオブジェクトが何をするのものなのかに注意を払う必要がある。つまり、オブジェクトの振る舞いを設計するということだ。振る舞いに適切な名前をつけて、ユビキタス言語の本質が伝わるものにしておきたい。

引用元:実践ドメイン駆動設計

上記にもあるとおり、モデリングにはコード設計の観点も必要です。
実際にモデリングした際は、確かに思考の半分は「オブジェクトを部品として利用できそうか」「単一責務になっているか」「組み合わせたときにユースケースの実装ができそうか」など実装イメージやコード設計になっていました。

効果が薄い箇所はモデリングしない

DDDはメリットも大きいですが、デメリットとして、ドメインモデリングや実装のコストが膨らんでしまうことが挙げられます。

業務システムには、そこまで重要な業務ルールが絡まないドメインオブジェクトや、データのみ管理したいドメインオブジェクトなどが中にはあります。
そのような場合、細かくモデリングして実装すると、デメリットがメリットを上回ってしまうこともあります。

DDDを駆動している原則
・コアドメインに集中すること。
ドメインの実践者とソフトウェアの実践者による創造的な共同作業を通じて、モデルを探究すること。
・明示的な境界づけられたコンテキストの内部で、ユビキタス言語を語ること。

引用元:エリック・エヴァンスのドメイン駆動設計

そのため、DDDの原則にもある通り、コアドメインではなく、DDDのメリットが薄い範囲に関しては、モデリングしないという選択肢も必要です。

コードに落とし込むまでモデリングは終わらない

もちろんデプロイした後も、常にドメインモデルは進化させていくことは大前提です。

ですが、開発フローを一部分切り取ってみてみると、今ある業務知識からドメインモデリングを終えたら、あとは実装だけと思い込みがちです。(私だけかもですが...)
しかし、実装に入った後に、重要なことや追加モデリングの必要性に気づくことは、やはり多々あります。

重大な発見はいつでも、設計や実装をするために努力する際に現れる

高度なスキルを持つ技術者が設計し、それほどスキルのない労働者が製品を組み立てる。このメタファは数多くのプロジェクトを台無しにしてきたが、理由は1つ、単純なことである。
すなわち、ソフトウェア開発は、すべてが設計なのだ。
分析、モデリング、設計、プログラミングのように責任を過剰に分離することは、モデル駆動設計の妨げになる。

引用元:エリック・エヴァンスのドメイン駆動設計

エヴァンスさんは「ソフトウェア開発はすべてが設計」と提唱しています。
開発に入ると、モデリングに戻る必要が出てきても、億劫さが邪魔をすることがたまにありますが、コードに落とし込むまでモデリングは終わっていないという意識を持つことは大事です。

本題とはずれますが、ドメインモデリングを行わなかった今までの開発フローの場合、実装に着手してから、仕様詰めやヒアリングの深掘りが必要な箇所に気づくことが多々ありました。 しかし、ドメインモデリングを行うようになったことで、実装前に限りなく細部まで設計が行え、手戻りが少なくなった、ということはとても実感しています。

序盤のドメインモデルは跡形もなく消える

まだ実践経験が少ないからかもしれませんが、序盤の方でモデリングした内容は、業務知識をアップデートしていく中で、大幅に変更されることが多いです。
これがエヴァンスさんの言うブレイクスルーなのかは判断が難しいですが、ドメインモデルはどんどん進化していくという心構え進化させる勇気(一度作ったものを壊す勇気)は必要です。

集約は可能な限り最小限に

・集約を大きくするメリット
 - 整合性を確保する実装とテストが簡単になる
・集約を大きくするデメリット
 - 処理するデータ量が増える
 - 排他制御の範囲が大きくなる

引用元:ドメイン駆動設計 サンプルコード&FAQ

モデル同士の整合性担保を考えていくと、気がつけば集約が大きくなっていることがあります。
集約が大きいと、上記以外にも「親エンティティクラスがとにかく肥大化する」などのデメリットがあるので、強い整合性担保の必要性がなければ、ドメインサービスに切り出したり、結果整合性を使うなどして、集約は可能な限り最小限にすることが推奨されます。

値オブジェクトは共通化の観点でもモデリングできる

エンティティや仕様オブジェクトなどは、オブジェクト間で振る舞いや属性などが同じでも、本質的には違う(責務が違う)ことが多いので、共通化という観点でモデリングすると、痛い目を見ます。
ただ、値オブジェクトに関しては、不変かつ交換可能で、さらにプリミティブな値の延長のようなオブジェクトであるため、同じコンテキスト内であればオブジェクト間で共通目的で利用されることが多く、共通化観点からも見つけることが可能です。

終わりに

今回は、連載1回目ということで、ドメインモデリングで具体的に行ったことや、実践を通して学んだことについてまとめました。
ドメインモデリングは型のようなものがあまりなく、方針から自身で試行錯誤する場面が多かったので、ぜひ一例として、これからDDDを始める方や今まさに実践している方の参考になれば幸いです。
ただ、私自身まだまだドメイン駆動設計に関しては経験が浅いため、今後も業務や実践で得た知見は引き続きアウトプットしていきたいと思います。

このようにスタイル・エッジでは、DDDなどのモダンな開発にも積極的にチャレンジできる環境です!
ご興味ある方はぜひ採用サイト ↓をぜひご覧ください ♪
recruit.styleedge.co.jp

次回は「【モダン開発 #2】DDDやレイヤードアーキテクチャを実現するために」を投稿します。お楽しみに~

【連載始めます】DDD・TDDに挑戦してみた!

はじめに

こんにちは!スタイル・エッジの SHISO です。
この度弊社では、数年間運用してきた業務システムをフルリプレイスする機会があり、 その中でドメイン駆動設計(DDD)やテスト駆動開発(TDD)などを取り入れたモダンな開発に挑戦してみました。

今回はこのような挑戦に至った経緯に触れつつ、
若手&モダン開発未経験メンバー中心の体制で進行するにあたり実際に行った取り組みや工夫した点を、 連載形式でご紹介していこうと思います。

経緯

今回フルリプレイスすることになった業務システムは、ありがたいことに約8年間クライアントの皆様にご利用いただいておりました。
快適にご利用いただけるよう、日々運用保守・機能開発を行なってきたのですが、だんだんと開発スピードが出せなくなってきている事実がありました。

主な原因として下記が挙げられます。

  • ビジネスロジックの分散・重複
  • 業務知識、システム仕様の属人化

このような状況が、今後の機能拡充においてボトルネックとなることが明らかだったため、DDDTDD、依存関係や責務を明確にしたレイヤードアーキテクチャなどを取り入れた、よりモダンな開発手法でフルリプレイスすることになりました。

連載予定

投稿日 タイトル
9/27 【モダン開発 #1】「ドメインモデリング」こんな感じでやりました
11/17 【モダン開発 #2】実践!DDD × レイヤードアーキテクチャ in Laravel
1/29 【番外編】気が利くDTO!データオブジェクトライブラリ「Laravel-data」を導入してみた
2/29 【モダン開発 #3】TDDに挑戦してみた!
3/28 【モダン開発 #4】モダン開発で工夫した8個のこと

※内容や更新予定日は、追加・変更の可能性があります。

続けて「【モダン開発 #1】「ドメインモデリング」こんな感じでやりました」も投稿します。お楽しみに~