MUGIJIRU.JP

Webエンジニアの雑談ブログ

マイクロサービスアーキテクチャ 用語メモ - 1

https://www.amazon.co.jp/dp/4873117607/ref=cm_sw_r_tw_dp_U_x_VhHRBb8BENJZCwww.amazon.co.jp


最近お勉強でこの本「マイクロサービスアーキテクチャ」を読んでいるのですが
横文字になかなかついていけなくて、evernoteにメモって一つ一つググりながら進めています。
今回はそのメモの一部を紹介します。

これらは、何言ってるのかサッパリわからないところから
ざっくりイメージを捉えるためにさっさとググってメモったものなので、間違っているかもしれません・・・

モノリス / モノリシック

一枚岩

コンウェイの法則

組織構造と設計は似てきちゃう。グダグダな組織構造だと設計もグダグダになりがち。
最適な設計を目指すなら組織構造も最適に。
(ここもっと理解したい)

チェックインをリリース候補として~

チェックイン = 要するにチェックアウトの逆、VCSに反映する内容。

コンシューマを変更することなく~

需要者 購入者 消費者 を変更することなく

レジリエンス

回復性 復元力 弾力性
不整合とかに強いってことか?

オンデマンドプロビジョニングシステム

リソースの調達を一括でやってくれるやつ AWS的な

サービスのセマンティクス

利用されている要素が正しく動作するかを判断する基準

フレーミング

枠づけ

メトリック

相手のところまでたどり着くのにどれだけ大変か (測量とかが語源)

エンドポイントのバージョニング

端末 拠点のバージョニング

サーキットブレイカ

良くない事象(つまり障害)が頻発すると「あ、これはやばいから一旦この導線をオフにしていこう」
という仕組み?

コードを介したガバナンス

コードを介した統治 / 支配

サイドカーサービス

親サービスと同じライフサイクルを共有し、親サービスと共に作成され、終了するサービス
アプリケーションのコンポーネントを別のプロセスまたはコンテナーにデプロイして
分離性とカプセル化を実現

デリバリチーム

作るチーム?

ドメインモデル貧血症

振る舞いとデータが分かれてしまっており、手続型の設計・実装になってしまう状態。
(ここもっと理解したい)

スタブ化

外部プログラムとの細かなインターフェース制御を引き受けるプログラム。



次回、続くかもしれません。

Macの.DS_Store、リソースフォーク(._で始まるファイル)を削除するオリジナルシェルコマンド

Macで共有フォルダをマウントして開発をしていると

.DS_Store
._ ファイル

が邪魔だなーと思うことがよくあります。

DS_Storeに関しては、あらかじめ作成しない方法があります。
下記でまとめてくださっている方がいらっしゃるので紹介
.DS_Storeの仕組みと削除&作成しないよう設定する方法 | UX MILK


でも、リソースフォーク(._ファイル)はFinderでマウントしているとどうしても作られてしまうことがあって、邪魔です。

そこで「rmrcforks (rm resource forksの略)」というコマンド一発で
current directory以下に存在する._ ファイルと .DS_Storeファイルを削除できるようにしてみました。

設定

# .bash_profile でパスが通っていること
PATH=$PATH:$HOME/bin
#!/bin/bash
# ~/bin/rmrcforks (755で作成)

OPT=${1:-0}

if [ ${OPT} = "-t" ] ; then
    find ./ \( -name ".DS_Store" -or -name "._*" \)
else
    find ./ \( -name ".DS_Store" -or -name "._*" \) -print -exec rm {} ";"
fi

実行

# 実際に消す
$ rmrcforks
# リストアップだけして消さない
$ rmrcforks -t

MySQL集計関数の小技(GROUP_CONCAT)

MySQLには「GROUP_CONCAT」という関数があります。
これには、複数のレコードを1行の文字列にカンマ区切りでつなげる能力があって
調査とか分析などでもたまに利用します。

どういう関数なのかは下記などよくまとめてくださっている方の記事をご参考に・・・
https://qiita.com/kyuu1999/items/93b02128f07c577b3e48

小技の紹介

この結果セットがカンマ区切りで文字として取れるのは周知のことと思いますが
実はこの関数、このまとまった文字列に対してソートとか重複排除ができたりします。

# 結果セットをidを基準に昇順で並べる
GROUP_CONCAT(bind_value ORDER BY id ASC)

# 結果セットを重複排除してユニークにする
GROUP_CONCAT(DISTINCT bind_value)

# 結果セットをユニークにしてidを基準に降順にする
GROUP_CONCAT(DISTINCT bind_value ORDER BY id DESC)

ちなみに、DISTINCTはCOUNTなどでも利用できます。

便利ですね!

Laravel5.3 以降 バージョンアップ MIddreware ConvertEmptyStringsToNullと日付系Validation

表題の件です。

アップグレードガイド 5.3 Laravel

配列、論理型、整数、数値、文字列をバリデートする場合、新しいnullableルールを指定していない限り、有効な数値として判断されなくなりました。

結論から言うと
Laravel5.3以降はバリデーションルールのrequireの逆は指定なしではなくnullable
という話です。

Laravel5.5のデフォルトのグローバルミドルウェア構成

下記のとおりです。

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];
...

ConvertEmptyStringsToNull とは何をしているミドルウェアなのか

読んだところ、リクエストパラメータが文字列かつカラ文字だったらnullに変換する処理ということがわかります。

<?php

namespace Illuminate\Foundation\Http\Middleware;

class ConvertEmptyStringsToNull extends TransformsRequest
{
    /**
     * Transform the given value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return mixed
     */
    protected function transform($key, $value)
    {
        return is_string($value) && $value === '' ? null : $value;
    }
}

Validationにおいて何が起こるのか

現在確認できている限り、日付系のバリデーションルールにおいて
常にfalseを返す動きを取るようになってしまうことが分かっています。

他のルールでも影響があるかもしれませんが全ては追っていません。

日付の範囲をフォームから受け取る、こんなバリデーションルールがあったとします。

<?php
$rules = [
    'date-from' => 'required|date',
    'date-to'   => 'date|after:date-from'
];

本来であれば

  • date_fromというパラメータは必須で、日付形式である必要がある。
  • date_toというパラメータは省略可能であるが、入力がある場合は日付形式かつdate_from以降である必要がある。

という動きをするはずです。

ですが、このConvertEmptyStringsToNullというミドルウェアが挟まると
afterのルールが入力の有無に関わらず常にfalseとなってしまいます!

なぜか

このConvertEmptyStringToNullミドルウェアを通ると

?date-from=2018-06-20&date-to=

というパラメータが

<?php
[
    'date-from' => '2018-06-20',
    'date-to'   => ''
];

ではなく

<?php
[
    'date-from' => '2018-06-20',
    'date-to'   => NULL
];

に変換されます。

すると、日付形式のバリデーション「compareDates」を利用するルールにおいては
値にNullが入っていると常にfalseを返すようになってしまいます。

<?php
# Illuminate/Validation/Concerns/ValidatesAttributes
...
    /**
     * Compare a given date against another using an operator.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  array  $parameters
     * @param  string  $operator
     * @return bool
     */
    protected function compareDates($attribute, $value, $parameters, $operator)
    {
        if (! is_string($value) && ! is_numeric($value) && ! $value instanceof DateTimeInterface) {
            return false;
        }

        if ($format = $this->getDateFormat($attribute)) {
            return $this->checkDateTimeOrder(
                $format, $value, $this->getValue($parameters[0]) ?: $parameters[0], $operator
            );
        }

        if (! $date = $this->getDateTimestamp($parameters[0])) {
            $date = $this->getDateTimestamp($this->getValue($parameters[0]));
        }

        return $this->compare($this->getDateTimestamp($value), $date, $operator);
    }

Laravel5.3以降は、このミドルウェアによって入力のないパラメータはNullとなり
DB更新と親和性が高まります。
パラメータの存在有無は、従来通りhasで確認することになるでしょう。
その代わり、requiredの逆にnullableを指定しないと、一部のルールが常にfalseを返すようになります。

Laravel5.2以前の動きに戻したい場合、このミドルウェアコメントアウトすれば元の挙動に戻ります。

バージョンアップ中に当現象に遭遇し、ソースを読んで変な時間を取ることになったので
似たケースで困る方の助けになれば幸いです。

csvファイルの処理

csvファイルの処理はシステム開発ではポピュラーなものですが
Microsoft Excelなどで吐き出されるような

・セル内改行を含んでいたり
・ダブルクォートで値が囲まれていたり

といったデータの処理については、初見ではどうすればいいのか悩むものでした。

私はいつも使う関数を持っているのですが、これが随分と信頼性があり
業務システムにおいてこの関数を介して処理したデータでトラブルが起きたことがありません。

fgetcsv, splfileobject, explodeによるお手製処理など
選択肢はいろいろありますが、どれも何らかトラブルが起きることが多い中で
この関数は安定感があるようです。

貼っておきます。

<?php
/**
 * CSVファイルの1行を配列で取得
 * 複数行対応
 *
 * @param object $fh fopen()などで得られたファイルハンドラ
 * @param string $file_encode 処理するファイルの文字コード
 */
function ml_fgetcsv(&$fh, $file_encode = 'UTF-8')
{
    $values = array();

    if (feof($fh)) {
        return false;
    }

    while ($values === array()) {
        $csv = '';

        while (!feof($fh)) {
            $csv .= fgets($fh);

            if (((preg_match_all('/"/', $csv, $matches))%2) == 0) {
                break;
            }
        }

        if (mb_strlen(trim($csv)) === 0) {
            continue;
        }

        $temp = preg_replace('/(?:\x0D\x0A|[\x0D\x0A])?$/', ',', trim($csv), 1);

        preg_match_all('/("[^"]*(?:""[^"]*)*"|[^,]*),/', $temp, $matches);

        for ($i = 0 ; $i < count($matches[1]); $i ++) {
            if (preg_match('/^"(.*)"$/s', $matches[1][$i], $m)){
                $matches[1][$i] = preg_replace('/""/', '"', $m[1]);
            }

            if (strtoupper($file_encode) !== 'UTF-8') {
                $values[] = mb_convert_encoding($matches[1][$i], 'UTF-8', $file_encode);
            } else {
                $values[] = $matches[1][$i];
            }
        }
    }

    return $values;
}

MySQL InnoDB Auto Increment

Auto Incrementは、Primary Keyのオプションです。
insertしたときに、自動的に増えていくアレです。

今回は、InnoDBにおけるこのAuto Incrementについて
認識しておいたほうがいいことをいくつか紹介します。

1. Auto Incrementは、原則1ずつ増える

id name
1 いちご
2 みかん

ここに、「りんご」をidの指定なしで追加するとidは自動的に3となります。

id name
1 いちご
2 みかん
3 りんご

※いくつずつ増えるかは、実は設定で変更できますが、めったに使わないのでここでは触れません。

2. Auto Incrementは、最大値 + 1ずつふえる

id name
1 いちご
2 みかん

ここに「ぶどう」をid = 5で追加すると、idは5になります。

id name
1 いちご
2 みかん
5 ぶどう

ここで、idの指定なしで「りんご」を追加すると、idは自動的に6となります。
3と4が空いていますが、ここを埋めるような動きはしません。

id name
1 いちご
2 みかん
5 ぶどう
6 りんご

3. Auto Incrementの最大値は、起動中は削除の影響を受けない。原則メモリが持っている。

id name
1 いちご
2 みかん
5 ぶどう

ここで、5のみかんを物理削除します。

id name
1 いちご
2 みかん

この状態で、idの指定なしで「りんご」を追加すると、idは自動的に6となります。
1,2しかありませんが、最大値はテーブル自体が持っているということです。

id name
1 いちご
2 みかん
6 りんご

4. テーブル自体が持っているAuto Incrementの値は、サーバの再起動でリセットされる!

これが要注意です!再起動が挟まると、3. の動きが変化します。

id name
1 いちご
2 みかん
5 ぶどう

ここで、5のみかんを物理削除します。

id name
1 いちご
2 みかん

ここで、サーバを再起動してから、idの指定なしで「りんご」を追加すると・・・
idは、6になってほしいところですが、なんと3になります。

id name
1 いちご
2 みかん
3 りんご

これはヤバい。(知らないと)
ユニークである必要があり、外部キーとして重要な意味を持つデータとして使われている場合は
予期しない動きを誘発することになるでしょう。

何この動き。

MySQL8.0で修正されたそうです。
5.7まではこの動きをします。要注意!

対策の候補

あまりありません。
・8.0にする
・外部キー制約を使う
レコードが物理削除されるテーブルではAuto Incrementに頼らない設計、実装にする

サーバの再起動のタイミングは厳密にコントロールできるものではありませんので
性質を理解して使う必要がありますね。要注意です!

Seederでレコードをループしながら色々やるときの話

LaravelのSeederの小ネタ。

データの変換やら何やらで、特定のテーブルの行をループしながら処理をするという場面はたくさんあると思います。
・レコード数が多いと全行getではメモリが爆発するのでページングする機会も多いはず。
・なんか実行中に進捗やら何やら出したいときもあるはず。

そんな時の書き方の例を紹介します。

<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Builder;

class SampleSeeder extends Seeder
{
    // 実行
    public function run()
    {
        $this->command->info('[!]レコードをループします');

        // データを取得するためのbuilder
        $builder = \App\Shouhin::query();

        // 当バッチ専用の絞り込み条件とか
        $scope = function(Builder $builder) {
            return $builder->withTrashed();
        };

        // 進捗バーの生成
        $progress_bar = $this->command
            ->getOutput()
            ->createProgressBar($this->pagerCount($builder, $scope));

        // ページング
        $page = 1;

        while(($rows = $this->pager($page, $builder, $scope))->count()) {
            // 行ごとのループ処理
            foreach ($rows as $row) {
                // 何かやるならここで。

                // 進捗バー
                $progress_bar->advance();
            }

            $page ++;
        }

        // 進捗バー終了
        $progress_bar->finish();
        $this->command->line(' done.');

        $this->command->info('[!]レコードのループが終わりました');
    }

    /**
     * ページング処理
     *
     * @param integer $page       ページ(1始まり)
     * @param object  $builder    Builder
     * @param object  $scope      第一引数にbuilderを取るクロージャ(where句の追加等)
     * @param integer $per_page   1ページあたりのレコード数
     *
     * @return Collection ページごとのレコード群
     */
    public function pager($page, Builder $builder, Closure $scope = null, $per_page = 5000)
    {
        return $this->addPagerScope($builder, $scope)
            ->skip(($page - 1) * $per_page)
            ->take($per_page)
            ->get();
    }

    /**
     * 行数カウント処理
     *
     * @param object $builder Builder
     * @param object $scope   第一引数にbuilderを取るクロージャ(where句の追加等)
     *
     * @return integer 件数
     */
    public function pagerCount(Builder $builder, Closure $scope = null)
    {
        return $this->addPagerScope($builder, $scope)->count();
    }

    /**
     * builderにスコープを当てる
     *
     * @param object $builder Builder
     * @param object $scope   第一引数にbuilderを取るクロージャ(where句の追加等)
     *
     * @return scope適用後のBuilder
     */
    public function addPagerScope(Builder $builder, Closure $scope = null)
    {
        if ($scope !== null) {
            $scope($builder);
        }

        return $builder;
    }
}

で、実行するとこんな感じ

$ php artisan db:seed --class=SampleSeeder
[!]レコードをループします
 1518/1518 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% done.
[!]レコードのループが終わりました
$ 

応用していって、builder, scope および一行ごとの処理を差し替えるような構造にすれば
制御に関する行がぐっと減ります。
Modelやら何やらごとの処理を集中してサクサク作っていけるのではないかと思います。