ytake Hatena

Web Application Developer

PHPカンファレンス福岡2018で「Event Sourcing, CQRS For PHP」の話をしました

先日開催されたPHPカンファレンス福岡2018

f:id:ytakezawa:20180704013557j:plain

いいカンファレンスを開催しよう!という福岡の皆さんの気持ちがとても良いカンファレンスで、
今年も登壇しつつ、いろんなセッションに参加したり、 いろんな方と技術なトークができて最高でした。

phpcon.fukuoka.jp

トーク内容

今年はタイトルにもある通り、「Event Sourcing, CQRS For PHPという話をしました。
DDDと一緒に紹介されたりすることも多いものですが、
データ処理方面からの話が主の内容でお届けしました。

ビックデータの処理、といえばphp以外の言語で処理することがほとんどですが、
こういったミドルウェアアーキテクチャを組み合わせることで、 目の前にある課題をシンプルに解決していくためのヒントになることも多く、
データ周りが複雑で、どうやったらいいんだろう、という方にも参考になれば幸いです。

この辺りのテーマは一昨年、去年、今年としばらくデータ処理系の開発や、 アーキテクチャを考えることが多く、
そこで実際に導入した方法や苦労したことなどをphpを通して話をしてるシリーズの一つです。

ytake.hateblo.jp

デブサミ2018 [Apache Kafkaによるスケーラブルアプリケーション開発] で登壇してきました #devsumi

トーク終了後、Ask the Speakerでもたくさんの方が質問してきてくれたり、
ミドルウェア周りの悩みをみんなで話したり、今回この話を選択してよかったなぁと思いました。

Twitterなどでも色々反応いただけてありがとうございました。

参加したセッション

skaffold を使って Kubernetes してみたLaravel Queue でスケーラブルなバッチシステムの構築

PHPerのためのよくわかるCPU脆弱性解説DDoS攻撃との終わりなき戦い

PofEAAで読み解くDoctrine2AI(人工知能)に聞いてみた! - クラウドサービスではじめる動画解析/音声変換 -

そして最後の 物理層のこと、時々でいいから、思い出してください。

でした。

DDoS攻撃物理層の話が特に印象に残りました。
また来年のカンファレンスも楽しみにしています!

カンファレンス前後

カンファレンス前後はやっぱりいつものところでphper大集結でした

f:id:ytakezawa:20180704013547j:plain

ラーメン食べたり

f:id:ytakezawa:20180704013601j:plain

懇親会では良いビールがあったり

f:id:ytakezawa:20180704013639j:plain

今回は会社のステッカーを持って行きました

f:id:ytakezawa:20180704013611j:plain

最高!

そして Laravel JPカンファレンス

さらっとここに記載しますが、
来年Laravel JPカンファレンスを開催します。
東京で開催しますが、2トラック進行とLaravel相談会を用意する予定です。
Laravelづくしのトラックと、アプリケーション開発・設計におけるトラックを考えていますが、
まだわかりません。

こちらはこちらでお楽しみに・・!

HHVM/Hack マイクロフレームワークにCache追加

ytake.hateblo.jp

以前から作ってたフレームワークで、
都度Cache組み込むのが面倒なのと、HackでPSRに準拠する必要もないだろうということで、
JavaEhcacheっぽい名前(名前だけ) のものを作り、
マイクロフレームワークに組み込みました。

github.com

HHVM/Hackにはmemcachedとredisのエクステンションが含まれているので、
なにもしなくても使えるのが楽で良いです。

後日 Mcrouter 対応ドライバも追加してリリースする予定です。

簡単な使い方

使いたいキャッシュドライバーをCacheManagerで指定すると、
そのキャッシュドライバが利用できるようにしています。

が、ドライバー単体でも利用できます

<?hh
use Nazg\HCache\Element;
use Nazg\HCache\CacheManager;

$manager = new CacheManager();
$cache = $manager->createCache('memcached');
$mc = new \Memcached('mc');
$mc->addServers([['127.0.0.1', 11211]]);
$cache->setMemcached($mc);
$cache->createCache('map')?->save('cache', new Element('testing', 0));

独自ドライバ追加方法

Nazg\HCache\CacheProvider クラスを継承していればなんでも使えます
ドライバ実装は下記の通りです。

<?hh // strict

use Nazg\HCache\CacheProvider;

class NullCache extends CacheProvider {
  <<__Override>>
  public function fetch(string $id): mixed {
    return;
  }
  <<__Override>>
  public function contains(string $id): bool {
    return false;
  }
  <<__Override>>
  public function save(string $id, Element $element): bool {
    return true;
  }
  <<__Override>>
  public function delete(string $id): bool {
    return true;
  }
  <<__Override>>
  public function flushAll(): bool {
    return true;
  }
}

上記の登録後は、以下の通りに記述するだけで利用できます。
簡単

<?hh

use Nazg\HCache\Element;
use Nazg\HCache\CacheManager;

$manager = new CacheManager();
$manager->addCache('null', () ==> new NullCache());
$manager->createCache('null'); // return NullCache

Hackで動的メソッドコール実装するヒント

Hackでは基本的に動的メソッドはtype checkerでエラーになりますが、
type checkerが理解できるように記述すればOKです。

CacheManagerは下記の実装になっています

<?hh

  protected Map<string, classname<CacheProvider>> $cache = Map {
    'apc' => \Nazg\HCache\Driver\ApcCache::class,
    'void' => \Nazg\HCache\Driver\VoidCache::class,
    'map' => \Nazg\HCache\Driver\MapCache::class,
    'file' => \Nazg\HCache\Driver\FileSystemCache::class,
    'memcached' => \Nazg\HCache\Driver\MemcachedCache::class,
    'redis' => \Nazg\HCache\Driver\RedisCache::class,
  };

  protected Map<string, (function():CacheProvider)> $userCache = Map{};

  public function createCache(string $namedCache): ?CacheProvider {
    if($this->cache->contains($namedCache)) {
      $cache = $this->cache->at($namedCache);
      return new $cache();
    }
    if($this->userCache->contains($namedCache)) {
      $cache = $this->userCache->at($namedCache);
      return $cache();
    }
    return null;
  }

CacheProviderでは実は下記のようになっており、継承に制約をつけています。

<?hh // strict

namespace Nazg\HCache;

use Nazg\HCache\{Cacheable, FlushableCache};

<<__ConsistentConstruct>>
abstract class CacheProvider implements Cacheable, FlushableCache {

}

コンストラクタに何かを付け加えることはできませんので、
インスタンス生成方法が統一されていますので、type checkerが 通ってよし と判断しています。

フレームワークへの組み込み

Containerにインスタンス生成方法等を登録します。

設定値はクラスでは関与せず、外から与えるものとしてshapeで縛っています。

<?hh

abstract class CacheServiceModule extends ServiceModule {
  protected Driver $defaultDriver = Driver::File;
  <<__Override>>
  public function provide(FactoryContainer $container): void {
    $container->set(
      CacheManager::class,
      $container ==> new CacheManager(),
      \Ytake\HHContainer\Scope::SINGLETON,
    );
    $container->set(
      CacheProvider::class,
      $container ==> {
        $manager = $container->get(CacheManager::class);
        if($manager instanceof CacheManager) {
          return $this->detectCacheProvider(
            $manager->createCache($this->defaultDriver),
            $this->cacheConfigure($container)
          );
        }
        throw new \RuntimeException("Failed to resolve " . CacheProvider::class);
      },
      \Ytake\HHContainer\Scope::SINGLETON,
    );
  }

  abstract protected function cacheConfigure(
    FactoryContainer $container
  ): CacheConfiguration;

  protected function detectCacheProvider(
    ?CacheProvider $provider, 
    CacheConfiguration $cacheConfigure
  ): CacheProvider {
    invariant($provider instanceof CacheProvider, "provider type error");
    if($this->defaultDriver === Driver::File) {
      if($provider instanceof FileSystemCache) {
        $dir = $cacheConfigure->getFileSystemDir();
        if(!is_null($dir)) {
          $provider->setDirectory($dir);
        }
      }
    }
    if($this->defaultDriver === Driver::Memcached) {
      if($provider instanceof MemcachedCache) {
        $m = $cacheConfigure->getMemcached();
        if(!is_null($m)) {
          $provider->setMemcached($m);
        }
      }
    }
    if($this->defaultDriver === Driver::Redis) {
      if($provider instanceof RedisCache) {
        $r = $cacheConfigure->getRedis();
        if(!is_null($r)) {
          $provider->setRedis($r);
        }
      }
    }
    return $provider;
  }
}

このクラスを継承し、利用者がどこから設定値を持ってくるかを指定すれば適切に起動します。
設定値のshapeは下記の通りです。

<?hh // strict

namespace Nazg\Cache;

type MemcachedServer = shape(
  'host' => string,
  'port' => int,
  ?'weight' => int,
);
type FileSystemConfig = shape(
  'cacheStoreDir' => string
);
type MemcachedConfig = shape(
  'servers' => ImmVector<MemcachedServer>,
  ?'persistentId' => string,
);
type RedisConfig = shape(
  'host' => string,
  ?'port' => int,
  ?'password' => string,
  ?'prefix' => string,
  ?'readTimeout' => float,
  ?'persistent' => bool,
  ?'database' => int
);

Skeletonへの組み込み

フレームワークのSkeletonを使えばこの辺りはデフォルトで対応しています。

github.com

設定値は下記で用意しています。(cache.global.php)

<?hh

use Nazg\Cache\Driver;
return [
  \Nazg\Foundation\Service::CACHE => [
    /*
     * Supported "apc", "map", "file", "memcached", "redis", "void"
     * Driver::Apc, Driver::Map, Driver::File, Driver::Memcached, Driver::Redis, Driver::void
     */
    'driver' => Driver::Memcached,
    'drivers' => [
      Driver::File => shape(
        'cacheStoreDir' => __DIR__ . '/../storages/cache/',
      ),
      Driver::Memcached => shape(
        'servers' => ImmVector{
          shape(
            'host' => '127.0.0.1',
            'port' => 11211,
            'weight' => 100,
          ),
        },
        // 'persistentId' => 'rename',
      ),
      Driver::Redis => shape(
        'host' => '127.0.0.1',
        'port' => 6379,
        'database' => 0,
        // 'password' => '', // optional
        // 'prefix' => '', // optional
        // 'readTimeout' => 1, // optional
        // 'persistent' => false, // optional
      ),
    ],
  ],
];

PHPerKaigi2018 Hackについて、と Laravel相談会司会担当で参加しました

*先日開催されたデブサミ2018での発表 [Apache Kafkaによるスケーラブルアプリケーション開発] については、
会社のブログに記載していますので、そちらを参照ください。

PHPerKaigi 2018は最高だった

お世辞抜きで良いイベントでした。
数週間前にデブサミという大きなイベントで登壇していたのもありますが、
規模感がすごくよかった。

それに各登壇者の方々(自分は除き)が、日本のPHPの第一人者といってもおかしくない方々と、
PHPと関わりのあるデータベース界隈から最高な方々が集まり、
参加した全ての方にいい刺激になったイベントだったのでは、と思います。

タイムテーブルの関係でTrack Aのほとんどのセッションに参加できませんでしたが、
ベストトーク賞に選ばれた後藤さんの内容は忘れないように気をつけていきたいと思います。

speakerdeck.com

Laravel相談会司会担当

phperkaigi.jp

3/10の朝一からLaravel相談会の司会を担当していました。

最初は数人から始まったのもあり、
純粋な質問会だったり、実装相談みたいなところから始まりましたが、
参加された方同士で意見交換が行われたり、
実際に使っている情報だったりが出てきて、相談のテーマが広がっていったり
まさにKaigi的な発展をしていったのがすごい楽しかった。

参加者の方々同士のコミュニケーションに発展していったり、
よくあるカンファレンスの一方向のトークではなく、
まさにsocket.ioみたいな双方向通信のコミュニケーションが生まれていくのは、
イベントとしてもかなりいい体験の提供ができる可能性があると思いました。

海外のカンファレンスで同様のものがあって、とりあえずやってみた、という長谷川さん!
やらせていただいてありがとうございました。

他のイベントでも是非またやりたい・・・!

自分は登壇よりもこういった相談会的なものの方が、
いろんな方の話が聞けるし、自分の糧にもなるので好みです。
*自社でもそういうことをしたりしているのもありますが

Hackについて話した

内容は以前にブログに書いたことの裏側です。

ytake.hateblo.jp

ytake.hateblo.jp

Hack自体は結構前から好きで使っていて、
実際に導入した時に今後使い続ける時に、
分離が進む中でPHPフレームワークに左右されるのも嫌だなーという思いから、
Zend Expressiveの挙動が好きだったのと、
LaravelのServiceProviderライクにシンプルに依存定義したい、
というのを混ぜてHTTP Request/ResponseとMiddleware以外の概念を取っ払ったものをHack専用に作り、
興味がある人のきっかけになればいいな、
という想いから今回そのテーマで話すことにしました。

Hackを気軽に始めるには、
数年前のHHVMとPHPの関係性の知識だけでは古くなっているのもあり、
最近の事情だったり、ライブラリを混ぜて紹介しながら、
実際の例も混ぜてみました。
strictにしなければ、結構簡単だったりしますので、
興味持った方は是非チャレンジしてみてください。

現在のPHP界隈で言われる型を意識した実装や、
テーマに興味がある方は結構マッチすると思っており、
逆にゆるふわなところが好みであれば戸惑うことも多いと思います。

Q&A

発表後にいくつかQ&Aがありました。
(いつも自分の発表にQ&Aがあまりないので嬉しかった・・)

バックボーンなどについて

PHPとHackは似ています、がやはり解決したいものが違うため、
JavaScalaのような関係だと自分自身は思っています。

どういう時にHackを選択するのか

I/Oが発生する通信や、MySQLへの問い合わせなど、
今回のトークにはありませんがAsync Awaitがあったりしますので、
そういった部分や、
PHPよりももっと堅実なアプリケーション開発を行う場合や、
Hackではコード内にHTMLを含ませることができなかったり(XHPで)、
XHP自体も脆弱性が生まれないように考えられていたりしますので、
そういったものがマッチすれば十分に選択肢となるのではないでしょうか?

実際に開発で利用するとわかると思いますが、
VectorなどのCollectionは使い勝手もよく堅くなりますので、
コード量が減る、というのもあります。
不確実な型の判定がなくなりますので、これだけでも十分だと思います。

初心者に勧められるかどうか

人によっては勧められる、といえるかもしれませんが、
自分自身 突然Rustを勧めるようなものだと思っており、
型が厳格なためチームでのアプリケーション開発はかなり良いと思っていますが、
教育コストと、導入者自身が理解していないと難しいと思いますので、
必ずしもYesとは言い難く、
HHVM/Hackにはたくさんの概念があり、まずはPHPを触れてみて、というのが良いかなと思います。

ちなみに実はそこまでパワープレイしていません!!!(笑)

当日の資料はこちら

speakerdeck.com

残念・・・!

健康について

去年ヘルニアで入院+しばらく休んでいたのでその体験を元に10分喋りました。

気をつけましょう!

当日の様子はこちらをどうぞ

togetter.com

今年のPHPイベントは始まったばかり!
次のイベントも楽しみですね!
いくぞ!福岡!(いきます

HHVM/Hack Nazgフレームワーク Validationの巻

HHVM/Hack向けに作ったオレオレマイクロフレームワークにおける
HTTPリクエストのバリデーション実装方法を紹介したいと思います!
Hackならではの機能を使ってバリデーションの仕組みを用意しています。

ytake.hateblo.jp

残念ながらLaravelのような細かいバリデーションルール指定方法などは用意していません
アプリケーションに合わせてひたすら実装するべし!
HackではPHPのような動的なメソッドコールはstrictで利用するとtypecheckerに怒られます
回避方法はありますが、実装していくとstrictにしたくなるものです・・

それはさておき

このフレームワークでは、バリデーションはこうしてください、というルールは特に持っていませんが、
専用に用意したAttribute を記述することで
Laravelのフォームリクエスト のような挙動で、
バリデーションを実行することができます。
*Annotationだと思ってください

Validation対象のActionクラス

routeを '/contents/{content}' として、このエンドポイントにアクセスした時にバリデーションを実行するようにします。
下記のようなクラスを作成します

ここに記述されているZend\Diactoros\Response\TextResponseクラスを利用してレスポンスを返却していますが、 Psr\Http\Message\ResponseInterface を実装していればなんでも構いません。

<?hh // strict

namespace App\Action\Document;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\TextResponse;

final class ReadAction implements MiddlewareInterface {

  public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
  ): ResponseInterface {
    return new TextResponse('Hello world!');
  }
}

route登録は、 config/routes.global.php ファイルに記述します。

<?hh 

return [
  \Nazg\Foundation\Service::ROUTES => ImmMap {
    \Nazg\Http\HttpMethod::GET => ImmMap {
      '/contents/{content}' => ImmVector {
        App\Action\Document\ReadAction::class
      },
    },
  },
];

忘れずにServiceModuleクラスにインスタンス生成方法を記述しましょう。

<?hh // strict

namespace App\Module;

use App\Action;
use Ytake\HHContainer\Scope;
use Ytake\HHContainer\ServiceModule;
use Ytake\HHContainer\FactoryContainer;

final class ActionServiceModule extends ServiceModule {
  <<__Override>>
  public function provide(FactoryContainer $container): void {
    $container->set(
      Action\Document\ReadAction::class,
      $container ==> new Action\Document\ReadAction(),
      Scope::PROTOTYPE,
    );
  }
}

これでrouteの準備ができました。

http://お好きなdomain/contents/aaaaa などでアクセスできます。

Validationクラスを作る

ここではstringの値が送られているかどうか、というバリデーションを例にしますが、
せっかくなのでHackのshapeを利用して型チェックバリデーションとして実装します。

type-assert install

まずは hhvm/type-assert をインストールします。

github.com

composer require hhvm/type-assert

PHPもインストールされている環境で、
上記コマンドでうまくインストールできない方は下記のようにするといいかもしれません

$ hhvm -d xdebug.enable=0 -d hhvm.jit=0 -d hhvm.hack.lang.auto_typecheck=0 $(which composer) require hhvm/type-assert

バリデーションクラスを、 App\Validation\ContentRequestValidator クラスとして作成します。
Nazg\Foundation\Validation\Validator クラスを継承して実装します。

<?hh // strict

namespace App\Validation;

use Facebook\TypeAssert;
use Nazg\Foundation\Validation\Validator;
use Psr\Http\Message\ServerRequestInterface;

final class ContentRequestValidator extends Validator {
  
  const type ContentRequestShape = shape(
    'content' => string,
  );
  
  protected bool $shouldThrowException = true;
  
  protected Vector<string> $errors = Vector{};

  <<__Override>>
  protected function assertStructure(): void {
    try {
      TypeAssert\matches_type_structure(
        type_structure(self::class, 'ContentRequestShape'),
        $this->request?->getAttributes(),
      );
    } catch (TypeAssert\IncorrectTypeException $e) {
      $this->errors->add("type error");
    }
  }

  protected function assertValidateResult(): Vector<string> {
    return $this->errors;
  }
}

ContentRequestShape

ContentRequestShapeは、リクエストで受け取る値をshapeを使って型を記述しています。
shapeはGoのstructのようなものだと思っておくと理解しやすいかもしれません

shouldThrowException property

フレームワークで、バリデーションエラー時はExceptionを投げないようになっています。
trueにすることでExceptionHandlerクラスで自由にレスポンスを操作することができます。
Laravel/LumenのExceptionHandlerクラスの使い方とほぼ同じです。

assertStructure、assertValidateResultメソッド

フレームワークで用意しているバリデーションで、
型チェックと値自体のバリデーションの両方を実装することができるようになっています。

型チェックが先に実行され、assertValidateResultがバリデーション実行後の結果を返却します。
細かいバリデーションは、クラス内にそれぞれのバリデーションを行いたいメソッドを記述し、
assertValidateResultでそれらを実行し、結果を Vector<string> に詰める、という具合です。

<?hh 
    try {
      TypeAssert\matches_type_structure(
        type_structure(self::class, 'ContentRequestShape'),
        $this->request?->getAttributes(),
      );
    } catch (TypeAssert\IncorrectTypeException $e) {
      $this->errors->add("type error");
    }

Facebook\TypeAssert\matches_type_structure でHTTPリクエストの値が期待している型かどうかをチェックし、
期待していない型の場合は、 Facebook\TypeAssert\IncorrectTypeException がスローされるため、
Vectorに失敗したことを示す文字列を追加しています。

実装後このバリデーションクラスをServiceModuleクラスで登録します。

<?hh // strict

namespace App\Module;

use App\Validation;
use Ytake\HHContainer\Scope;
use Ytake\HHContainer\ServiceModule;
use Ytake\HHContainer\FactoryContainer;

final class ValidationServiceModule extends ServiceModule {
  <<__Override>>
  public function provide(FactoryContainer $container): void {
    $container->set(
      Validation\ContentRequestValidator::class,
      $container ==> new Validation\ContentRequestValidator(),
      Scope::SINGLETON,
    );
  }
}

ServiceModuleクラスはなんでも構いませんが、新たに作った場合はかならず config/modules.global.php に記述してください。

<?hh

return [
  \Nazg\Foundation\Service::MODULES => ImmVector {
    \App\Module\ActionServiceModule::class,
    \App\Module\ExceptionServiceModule::class,
    \App\Module\MiddlewareServiceModule::class,
    \App\Module\LoggerServiceModule::class,
    \App\Module\ValidationServiceModule::class,
  },
];

これでバリデーションの準備が整いました。

バリデーション実行

バリデーションを実行したいクラスのメソッドに Attribute を記述します。

先ほど作成した App\Action\Document\ReadAction クラスで実行するようにするには次の通りです。

<?hh

namespace App\Action\Document;

use App\Validation\ContentRequestValidator;
use App\Responder\IndexResponder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\TextResponse;

final class ReadAction implements MiddlewareInterface {

  <<RequestValidation(ContentRequestValidator::class)>>
  public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
  ): ResponseInterface {
    return new TextResponse('Hello world!');
  }
}

<<RequestValidation(ContentRequestValidator::class)>> この部分がバリデーション指示になります。
指定方法は <<RequestValidation(実行したいバリデーションクラス)>> となります。

最後にExceptionHandlerクラスで任意のレスポンスを返却するように記述すればOKです。
フレームワークのskeletonに継承した App\Exception\AppExceptionHandler クラスが含まれていますので、
そのクラスを利用します。

<?hh

namespace App\Exception;

use Nazg\Http\StatusCode;
use Nazg\Foundation\Validation\ValidationException;
use Nazg\Types\ExceptionImmMap;
use Nazg\Foundation\Exception\ExceptionHandler;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse;

class AppExceptionHandler extends ExceptionHandler {
  <<__Override>>
  protected function render(
    ExceptionImmMap $em,
    \Exception $e
  ): ResponseInterface {
    $message = $em->toArray();
    if($e instanceof ValidationException) {
      $message = $e->errors();
    }
    return new JsonResponse(
      $message,
      StatusCode::StatusInternalServerError,
    );
  }
}

Nazg\Foundation\Validation\ValidationException クラスがスローされた場合に返却されるレスポンスを変更しました。

誤った型が送信されるとjson[type error!] と返却されます。
バリデーションの実装は以上になりますが、
実は hack-routerで受け取ったリクエストの値は全てstringになるため、通常はこのバリデーションは絶対にエラーになりませんが、
アプリケーションで利用するものと異なるメソッドなどを指定した場合に発生しますので、
実装時に簡単なエラーなどを見つけることができるようになりますので、
色々チャレンジしてみてください。

以上、簡単なようでちょっと面倒臭いバリデーションの実装方法でした。

デブサミ2018でApache Kafka、PHPerKaigi 2018でHackの話をします

デブサミ2018!

開発者にはおなじみのデブサミですが、
今年は弊社(アイスタイル)もスポンサーとして参加し、急遽わたくしも登壇することになりました。
(先週決まりました・・・)

event.shoeisha.jp

event.shoeisha.jp

ゴールドスポンサー!

今年は登壇だけではなく、スポンサーなどの面でも積極的に参加できればと思っています。

肝心のトーク内容ですが、

Apache Kafkaによるスケーラブルアプリケーション開発 という、
去年のPHPカンファレンスと同タイトルですが、
今回はPHPの枠を取っ払い、Kafka ConnectやKafka Stremasを利用して複雑化した問題や、
データ処理といったところに軸を変えてお話しする予定です。
実際のScalaのコードなども混ぜて実例の紹介なども考えています。

http://event.shoeisha.jp/devsumi/20180215/session/1683/

興味がある方は是非ご参加ください!

つづいてこちら

PHPerKaigi 2018

今年初めて開催されるPHPerKaigi2018

phperkaigi.jp

iOSDCでお馴染みの長谷川さん(@tomzoh) 主催のカンファレンスです。
美味しいビールとPHPの話、まるで海外のカンファレンス様なイベントで、
開催が決まった時から楽しみにしていたイベントの一つですが、
今回そんな初回で Hack の話をさせていただくことになりました。

Hackに関連するトークは、実は2年くらい前からいくつかのカンファレンスに応募してましたが、
毎回違うトークが採用されていましたが、今回ついに・・・!

トークのタイトルは、 Hackで作るマイクロフレームワーク です。

phperkaigi.jp

今年の頭に公開したHack向けマイクロフレームワークが題材ではありますが、
フレームワークの紹介、ではなく、
PHPのライブラリやPSRをHackにも取り入れて、
普段実装しているPHPアプリケーションとほぼ同じ感覚で開発ができる様にするためのいくつかのノウハウや、
コードレビューなどで発揮する厳格モードとの付き合い方、
Hack向けComposer最適化の方法や、
TypeAssertを使った厳格なバリデーション機能(フレームワークで導入した機能ですが紹介します)、
そしておそらくほとんどの人が思うIDEについて(入力補完の方法など教えますよ!)。

これらを活用してみなさんもHackでライブラリやフレームワーク開発などにチャレンジしていただければと思い、
今回お話させていただきます! ちなみに弊社はプロダクション環境でHack製アプリケーションが稼働しています。

同日 LTで弊社のエンジニアもHackの話で登場します。

そして最後に、

Laravel Meetup Tokyo Vol.10

久しぶりに開催します。
PHPerKaigi2018の前日に開催です!!!
前回からまた半年ほど時間が空いてしまいましたが5.5がリリースされたこともあり、
利用者がかなり増えたこともあり、ユーザー同士の交流の場を設けようと思い、
開催となりました。

laravel-meetup-tokyo.connpass.com

定期的に開催しようと毎回思ってはいるものの、なかなか時間がなかったり。。。
そして主催者が最近Laravel/Lumenを選択することも減っているという

どれもきっと楽しいイベントになると思いますので、
是非お越しください!!!!

ここ一年ほどわたくしのアプリケーション開発は
GoのGoaEcho
PHPはほぼ Zend Expressive
それ以外はHack, ScalaでKafka, Sparkばかりになっ・・・

気軽にHackチャレンジ マイクロフレームワーク公開

PHPと分離し始めたHHVM/Hackですが、
折角なので多くの方が やってみた で終わらないように、
シンプルで薄いマイクロなフレームワーク、というか、
Web Applicationのボイラープレートと言ってもいいくらいの簡単なものを公開しました。

github.com

*名前は 指輪物語 より

機能を備えたフレームワークを作るよりも、
最近はコンポーネントなどを組み合わせる開発者も多いため、
最低限のリクエスト・レスポンス以外の機能を付け加える予定はありませんが、
とっかかりには小さくチャレンジできるのではと思います。

簡単な使い方などを紹介します。

HHVM環境構築

Ubuntu16.04などのサーバが手元にある方は、簡単にHHVMの環境が構築できます

Installation: Introduction

またはVagrantで簡単に構築することもできます。
公開しているytake/gardening-hhvmを利用すると、3.23.4の環境が起動します。

$ vagrant box add ytake/gardening-hhvm

Vagrantの詳細については gardening-hhvm#install-gardening-box

Dockerについては後日

install

composer を使ってcreate-projectをする場合は、次のコマンドを実行します。

$ hhvm -d xdebug.enable=0 -d hhvm.jit=0 -d hhvm.php7.all=1 -d hhvm.hack.lang.auto_typecheck=0 \
 $(which composer) create-project nazg/skeleton [アプリケーション名] --prefer-dist

依存しているPSR-15インターフェースがphp7以上となっているため、
PHP7モードで実行する必要があります。

簡単にAPIを作ってみよう

環境構築後は実際に実装するだけです。
簡単なAPIを実装します。

処理フロー

このフレームワークAPIやそこまで大きくない規模のアプリケーション利用を想定して、
ADRを採用しています。

まずはAction、Routerです。

Action

Zend Expressiveのようにこのフレームワークにおいても、
アクションはあくまで一つのミドルウェアにすぎず、PSR-15(現在ドラフト)を採用しています。

これにならって、まずはActionを用意します。
src/Action/ReadAction.php として下記のものを記述します。

<?hh

namespace App\Action;

use App\Responder\IndexResponder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;

final class ReadAction implements MiddlewareInterface {

  public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
  ): ResponseInterface {
    return new JsonResponse([]);
  }
}

Hackのコードだけであれば、 <?hh // strict で厳格モードで実装ができますが、
PHPのライブラリが含まれる場合は、厳格モードにせずに実装します
PSR-7に準拠したライブラリで、HHVM/Hackで動作するものであればなにを利用しても構いません。
デフォルトでは zendframework/zend-diactoros になります。

このActionをRouterに登録します。

Router

デフォルトでは config/routes.global.php に記述するだけです
GETリクエストで作用するActionとして下記の通りに記述します。

<?hh

return [
  \Nazg\Foundation\Service::ROUTES => ImmMap {
    \Nazg\Http\HttpMethod::GET => ImmMap {
      '/' => ImmVector{ App\Action\IndexAction::class },
      '/sample' => ImmVector{ App\Action\ReadAction::class },
    },
  },
];

Register Container

利用準備はこれで整いますが、
このフレームワークでは簡単なDependency Injectionをサポートしており、
インスタンスの生成方法を指定する必要があります。
LaravelのようなAuto Wiringはないため、記述していないクラスは生成することができません。

デフォルトでは src/Module/ActionServiceModule.php が用意されていますので、
そちらに追記します。

<?hh // strict

namespace App\Module;

use App\Action\IndexAction;
use App\Action\ReadAction;
use App\Responder\IndexResponder;
use Ytake\HHContainer\Scope;
use Ytake\HHContainer\ServiceModule;
use Ytake\HHContainer\FactoryContainer;

final class ActionServiceModule extends ServiceModule {

  public function provide(FactoryContainer $container): void {
    $container->set(
      IndexAction::class,
      $container ==> new IndexAction(new IndexResponder()),
      Scope::PROTOTYPE,
    );
    // 追加したActionのインスタンス生成方法を記述
    $container->set(
      ReadAction::class,
      $container ==> new ReadAction(),
      Scope::PROTOTYPE,
    );
  }
}

Scopeは指定しない場合は都度インスタンスを生成するPrototypeになりますが、
Singletonを望む場合は、 Scope::SINGLETON を指定してください。

Containerの詳細な使い方については、
ytake/hh-container を参照ください。

クラス追加時に忘れずにdump-autoload

このフレームワークはHackに最適化されたcomposerプラグインのhhvm-autoloadを利用しています。

github.com

クラス追加時は、以下のコマンドを必ず実行して、hh_autoload.phpに反映してください。

hhvm -d xdebug.enable=0 -d hhvm.jit=0 -d hhvm.php7.all=1 -d hhvm.hack.lang.auto_typecheck=0 $(which composer) dump-autoload

実行後は追加した /sample にアクセスしてみてください。
空のJson配列が返却されているはずです。

Hackならではの機能を使ってみよう

HackにはShapeという配列に対しての型をチェックするものがあり、
配列に対しても厳格さを要求することができます。

APIを開発する際に、あるカラムにstringやintが混在し、
AndroidiOSの開発者に注意されることなどもあるのではないかと思いますが、
そう云うケースや、バリデーションに利用することができます。

ここではレスポンスに対して、期待通りのレスポンスを返しているか
チェックするミドルウェアを追加してみましょう

TypeAssert

hhvm/type-assert を使って厳格に調べるように実装し、
configファイルで特定のrouteにのみ作用するように記述します。

<?hh // strict

namespace App\Middleware;

use Facebook\TypeAssert;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class TypeAssertMiddleware implements MiddlewareInterface {

  const type ReadStructure = shape('name' => string,);

  public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
  ): ResponseInterface {
    $response = $handler->handle($request);
    $decode = json_decode($response->getBody()->getContents(), true);
    TypeAssert\matches_type_structure(
      type_structure(self::class, 'ReadStructure'),
      $decode,
    );
    return $response;
  }
}

配列に期待する型をshapeで記述しています。
この例では、配列の中の name はstringであることとなります。

const type ReadStructure = shape('name' => string,);

先ほどの例と同様に、config/routes.global.phpや、
MiddlewareServiceModule.phpを作成し、アプリケーションに登録します。

MiddlewareServiceModule

デフォルトのActionServiceModule.phpと同様のクラスを作成します

<?hh // strict

namespace App\Module;

use App\Middleware\TypeAssertMiddleware;
use App\Responder\IndexResponder;
use Ytake\HHContainer\Scope;
use Ytake\HHContainer\ServiceModule;
use Ytake\HHContainer\FactoryContainer;

final class MiddlewareServiceModule extends ServiceModule {

  public function provide(FactoryContainer $container): void {
    $container->set(
      TypeAssertMiddleware::class,
      $container ==> new TypeAssertMiddleware(),
    );
  }
}

各configに追記します。

config/module.global.php

依存解決方法を記載したServiceModuleクラスを追加します

return [
  \Nazg\Foundation\Service::MODULES => [
    \App\Module\ActionServiceModule::class,
    \App\Module\MiddlewareServiceModule::class,    
  ],
];

config/routes.global.php

特定のrouteで作用するように、ImmVectorに追記します。
Actionクラスを挟み込むように作用させるには、Actionクラスよりも前に指定します。
Action, Middlewareはここで指定した順番で実行されます。

<?hh

return [
  \Nazg\Foundation\Service::ROUTES => ImmMap {
    \Nazg\Http\HttpMethod::GET => ImmMap {
      '/' => ImmVector{ 
        \App\Middleware\TypeAssertMiddleware::class, 
        \App\Action\IndexAction::class,
      },
      '/sample' => ImmVector{ App\Action\ReadAction::class },
    },
  },
];

これで /sample にアクセスすると 配列に期待している型とは異なっているため、
Facebook\TypeAssert\IncorrectTypeException がスローされます。

これを回避するため、src/Action/ReadAction.php で返却されるレスポンスを変更します。

<?hh // strict

// 省略
final class ReadAction implements MiddlewareInterface {

  public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
  ): ResponseInterface {
    return new JsonResponse(['name' => 'ytake']);
  }
}

これで /sample にアクセスすると期待通りの型になったことにより、
通常のjsonのレスポンスが返却されます。

今回は簡単なHackによるアプリケーション開発を紹介しました

New Year's Resolution 2018

2018年になりましたので、今年の抱負とか

アウトプット

登壇とか

引き続きPHP系のカンファレンスにはお邪魔しながら、
登壇なりをしていこうと思ってます。
去年はビッグデータ系のミドルウェアアーキテクチャが中心でした。

面白いテーマではありながらも、Hadoop周りのシステムや他言語もある程度理解していないと難しいものでした。

今年はやはり、Hackかなぁ・・・
shapeを使った構造体ライクなアプローチとかは結構面白いと思うので、
そのあたりを交えたり、Hack面白そうじゃん!というところをもっと共有していきたいですね

今年もbuildersconまたいきたい・なんか喋りたいなぁ

ライブラリ

去年からScala周りといっても主にSparkや Kafkaですが、
そのあたりのものを作ったり、提供したりしようと思っています。

Hackも折角なので簡単に使えるものをいくつか作ろうと思ってます
2017年末〜年明けは実はHackでADR的に作るなら、
ということですごい簡単な例を公開しながら開発中です。
まだ作っている途中なのと、どこかでマイクロフレームワークライクに切り出すかもしれません

github.com

書き物

がんばる・・・

インプット

知識がそのままで止まることがないように、引き続き意識してやっていこう
ただもうフロントエンド開発はほとんどやらないので、Reactで止める

健康

11月中旬から椎間板ヘルニアがどうにもならなくなって、保存療法で入院したりしてました。
悪化しないように気をつけよう

ヘルニアで休んでいた期間に感じたのは、子供の圧倒的な成長速度
毎日終電までオフィスにいた生活ではわからなかったくらい、日々変わって成長していく子供
全然触れ合わずに仕事だけするというのもあまりよくない事だなと実感
変えてこう

あとはずっとやりたかったことができそうになってきているので、
本気で取り組んでいく