embryo

エンジニアの備忘録

es6でカスタムイテレータを作る (Iterator, Iterable, Generator)

今日はes6の仕様であるIteratorIterableについてです。仕様を調べつつ実装して挙動を確かめていきます。

Iterator

一連の処理中において現在の処理位置を把握しつつ、コレクション中の項目へ一つずつアクセスする方法を指します。es6においては下記のnextメソッドを実装したものを指します。

※実際に下記のようなIteratorクラスが定義されているわけではありません。

class Iterator {
     next(): { done: boolean, value: any }
}

例えば以下のような実装はiteratorになります。

let iterator = {}
iterator.next = () => {
     return { done: false, value: 1 }
}

console.log(iterator.next().value) // 1
  • doneIteratorが完了したかを返す真偽値
  • valueIteratorから取り出した値

であり、donetrueで返ってくる場合、valueは省略されます。

Iterable

iteratorプロトコルとは別にiterableプロトコルというプロトコルが定義されています。iterableプロトコルを実装したオブジェクトはfor … of文が使用可能になります。

for … of 文はパラメータを列挙するオペレータで以下のように使用できます。

let iterable = [1, 2, 3]
for (item of iterable) {
     console.log(item)
}

// 1
// 2
// 3

具体的には下記の[Symbol.Iterator]メソッドを実装したものを指します。また返却値は前述したIteratorプロトコルになります。

class Iterable {
     [Symbol.Iterator](): Iterator
}

例えば以下のような実装はIterableになります。

let iterable = {}
iterable[Symbol.iterator] => {
     return next() => {
          return { done: false, value: 1 }
     }
}

Linked List構造のIterableを作る

Iterableプロトコルを実装することで、ビルトインのiterable以外のものを自前で実装可能であることがわかりました。 そこでLinkedListの構造を持ったIterableオブジェクトを作って試していきます。LinkedListの実装は下記に公開しています。

LinkedList.js · GitHub

class IterableLinkedList extends LinkedList {

  [Symbol.iterator]() {

    let node = this.head;

    return {

      next: () => {

        if (!node) {
          return {
            done: true
          };
        }

        let value = node.value;

        node = node.next;

        return {
          value: value,
          done: false
        };

      }

    };

  }

}

自身でカスタムイテレータを作る場合、やはり内部のコードは煩雑になってしまいますね。またIterable, Iteratorプロトコルを正しく理解していなければなりません。そこで次は同じくes6の仕様であるGeneratorを使って同じものを実装してみます。

Generatorを使用した実装

Generator関数はIteratorプロトコルに準拠しているため、Generator関数を用いることでよりシンプルにIterableを実装することが可能になります。メソッドに定義する場合は関数名の先頭に*をつけることでGenerator関数が私用可能になります。

class IterableLinkedList extends LinkedList {

    * [Symbol.iterator]() {

      let node = this.head;

      while (node) {
        yield node.value;
        node = node.next;
      }

    }

}

Generatorを使用することでnextメソッドの定義や、doneの制御などが不要になり、シンプルに記述することが出来ました。

参考

React+Typescriptでコンポーネント公開用のBoilerplateを作った

今日はReact製のコンポーネントを外部公開するための雛形を作成したのでその紹介です。 ソースは以下。

github.com

構成

Typescript

型が欲しいのでTypescriptを使用しています。普段も大体Typescriptで書いています。

Jest

単なるコンポーネントのテストが目的なのでシンプルなパッケージ構成で簡単にテストできる jest を使用しています。

Typescriptを設定する

テストも型安全に書きたいのでts-loaderを使用しています。package.jsonに以下の設定を追記します。

 "jest": {
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
    },
    "testRegex": "/__spec__/.*\\.(ts|tsx|js)$"
  }

レンダリング結果をテスト

Componentのレンダリング結果をテストするためにairbnb製の enzyme を使用しています React.TestUtilsでも問題ありませんがjest公式で説明されているためこちらを使用します。

GitHub - airbnb/enzyme: JavaScript Testing utilities for React

import Component from "../Component";
import * as React from "react";
import * as Enzyme from "enzyme";

describe("Component", () => {

  it("content", () => {

    // Render a component
    const component = Enzyme.shallow(<Component />);
    expect(component.text()).toEqual("Awesome component");

 });

});

Storybook

開発中の動作確認の他、StyleGuide生成のために storybook を使用しています。 他にも style-guidist, catalog など選定に上がりましたが、フレームワークとしてのルールやメンテの容易さから storybook を選定しました。

Typescriptを設定する

公式の説明通りにセットアップを済ませると、.storybook以下にファイルが生成されるのでカスタマイズしたい場合はファイルを編集します。

.storybook
├── config.js
└── webpack.config.js

私はes6で設定を記述したいのでwebpack.config.babel.jsを作成し、webpack.config.jsから読み込んでいます。実際の設定は以下のようになります。今回はts-loaderを追加し、Typescriptを使用できるようにしています。

import path from "path";
import genDefaultConfig from "@storybook/react/dist/server/config/defaults/webpack.config.js";

module.exports = function(baseConfig, env) {
  const config = genDefaultConfig(baseConfig, env);

  config.resolve.extensions.push(".ts", ".tsx");

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    include: [/stories/, /src/],
    loader: require.resolve("ts-loader")
  });

  return config;
};

Storyファイルを書く

stories配下にファイルを配置して、実際のデモを記述します。

import * as React from "react";

import { storiesOf } from "@storybook/react";
import { Component } from "../src/Component";

storiesOf("Components", module)
  .add("Awesome component", () => <Component />)

Storyファイルを読み込む

.storybook/config.jsを編集してファイルを追加します。UIや設定をカスタマイズしたい場合はオプションを設定します。

import { configure } from "@storybook/react";

const loadStories = () => {
  require("../stories/index.tsx");
}

configure(loadStories, module);

あとはnpm scriptにstorybookが追加されているので実行してブラウザで確認します。

参考

UniversalLinksの挙動について

OpenIDConnectのクライアント実装のためにUniversalLinksを使用したので、その挙動についてまとめました。

Webブラウザからアプリへの遷移方法

AppScheme

app://[*] でアプリへのリンクを実現しようというもの。被ったり敢えて被せてくるセキュリティリスクがあるため、iOS9以降では非推奨になっています。

UniversalLinks

アプリ起動のためのよりセキュアな仕様。詳しくは下記参照。

App Search Programming Guide: Support Universal Links

webサイトのURLとアプリケーションを紐付けるため名前被りの心配が不要になる他、関連付けをwebサーバーに問い合わせる必要があるためよりセキュアになります。

実装

1. apple-app-site-association をwebサーバーに設置する

appIDにはTeamIDBundle Identifierをつなぎ合わせたものを指定します。pathsには設定したいパスを設定します。特に指定しない場合は*を指定します。

"applinks": {
  "apps": [],
  "details": [
    {
      "appID": <TeamID>.<Bundle Identifier>,
      "paths": ["/path/to/website"]
    }
  ]
}

https配信に対応している場合は上記のファイルを設置するだけで完了です。(2の手順は必要ありません)

2. SSL証明書で署名する

1.で作成したファイルをhandoff.jsonとして保存し、キーチェーンアクセスから、iPhone Distribution 用の SSL証明書を書き出します(certificate.p12)。

証明書から秘密鍵を書き出します。

openssl pkcs12 -in certificate.p12 -out certificate.key -nocerts

証明書から公開鍵を書き出します。

openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes -clcerts -nokeys

handoff.json を署名します。

cat handoff.json | openssl smime -sign -inkey certificate.key -signer certificate.pem -certfile certificate.pem -noattr -nodetach -outform DER > apple-app-site-association

ここで出力したファイルをwebサーバーに設置すれば完了です。

3. アプリ側の設定

CapabilitiesでAssociated Domainsを有効(ON)にし、以下の値で設定します。 applinks:準備したwebサーバーのドメイン

4. アプリ側の処理

application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool を用いてアプリケーション側のハンドリングを追加します。 SearchAPI や Handoff にも反応してしまうため、activityTypeでフィルタリングする必要があります。

    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

        // Universal linksを処理します
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            // handle universal links
        }

        return true

    }

OpenIDConnectを実現する上での注意点

基本的にユーザー操作に起因しての遷移以外ではUniversalLinksによるアプリ起動は行われない仕様になっているため、状況に応じて以下のような対応が必要になります。(※iOS-Safariのアップデートによって挙動が変わる可能性があります。)

リソースプロバイダ側で認可前の場合

  • リソースプロバイダ側で入力必須であり、ログインボタンを明示的に押下する必要があるので問題なし
  • redirect_url先でUniversalLinksのURLにリダイレクトしても問題なし

リソースプロバイダ側で認可済みの場合

  • リソースプロバイダ側で「アプリに戻る」ボタンを設置
  • もしくはUniversalLinks対象の画面に「アプリに戻る」ボタンを設置

ちなみにfacebookなども認可済みの場合は「アプリに戻る」ボタンを設置しています。

その他

apple-app-site-association の設置状況や内容が正しい状態になっているかの確認は下記チェッカーを使用しました。うまくいかない場合はこちらで確認してみると便利です。

Universal Link Validator

参考

FluxとDDD(レイヤードアーキテクチャ)について考えてみた

トレタ Advent Calendar 2016 - Qiita 16日目の記事になります。 フロントエンドエンジニアのすえだです。

はじめに

  • Flux実装する上で曖昧性をできるだけ無くすために頑張る話です。
  • ***アーキテクチャはこうあるべきみたいな原理主義的な話はありません。あくまで参考です。

この記事で書いていること

  • Fluxについて
  • DDD(レイヤードアーキテクチャ)について
  • FluxとDDDの関係性
  • Fluxのレイヤー化

Fluxについて

f:id:sue71:20161215193554p:plain

単方向に伝搬されるデータでアプリケーションの状態を表現するアーキテクチャパターンです。 CQRSとEvent Sourcingを組み合わせたような形になります。

恩恵

  • 単方向のデータフローにより、行ったり来たりのような処理が少なく振る舞いを理解しやすい
  • DispatcherやActionがシングルトンで表現されるので参照に悩まされづらい

DDD(レイヤードアーキテクチャ)について

f:id:sue71:20161215195056p:plain

DDD自体はドメインモデルを中心に置いたサービス設計手法とのこと。 実装においてはドメインモデルと技術的関心を分離するために 上記のようなレイヤードアーキテクチャが推奨されています。 (Clean Architectureなどいくつか発展型や派生系がありますがここでは基礎となるこれだけを紹介します)

Infra

定義の上では「上位のレイヤーを支える技術的な基盤」とあり、ライブラリやフレームワークなんかもここに含まれるのですが 本記事では簡単のためにDBやAPIなどの永続化を担う層として考えます。

Domain

いわゆるビジネスロジックになりますが 噛み砕いて言うと「あるデータとその振る舞い」を表現する層になります。

Application

アプリケーションに依存する振る舞いです。 ドメイン層のオブジェクトを協調させるような処理を担当します。

Presentation

UIなどユーザーにとってどう見せたいかを実現する層です。

恩恵

  • 責務が分離されているので実装時に迷うことがない
  • レイヤーが分離されているのでフレームワークに依存せず、リプレイスも容易

FluxとDDD

Fluxの各レイヤーをDDDの視点で整理していきます。

View

Presentation層にあたります。理解しやすいので説明を割愛します。

Action

その名の通り「振る舞い」を記述する部分になります。 APIと疎通し、取得したデータをStoreに伝搬するなど、ある振る舞いに依存するドメインモデルの協調動作を取るような働きをしています。このことからActionはアプリケーションロジックを担っていると言えそうです。

Store

Actionから受け取ったイベントをもとにアプリケーションの状態を変更し、Viewに状態の変更を伝えます。 名前から永続化を担当するInfra層かのように見えますがAPIとの疎通は基本的にActionが行うため、永続化の責務はAction(もしくはActionから実行されるInterface)が担っているように見えます。

しかしながらActionはオンメモリのドメインオブジェクトの集合への参照はもっていないため、バリデーションなどのロジックはStoreが受け持つことになり、ドメインのロジックを含んでいると言えます。

また実装によってはDBへの書き込みはStoreで行ったり、Viewからの参照があるためにViewModelのような属性を持ったりすることもありえるでしょう。

このようにStoreの責務が定まっていないことがFlux実装の曖昧性の原因ではないでしょうか。

Fluxのレイヤー化

そこでStoreの責務に焦点をあてて構造化を目指します。

方針

  • Flux実装における Storeの責務が曖昧なので明確にしたい、また必要であれば分割したい
  • Fluxの良さをできるだけ損なわずにDDDの思想を取り入れたい
  • Storeの破棄についてのケア

3つ目は本記事の内容とズレますがここをカバーしていないと夢物語になりそうなので入れておきます。

こんな感じになった

名前は適当です。

f:id:sue71:20161216065620p:plain

Repository

Repositoryパターンの解釈が分かれるところかとは思いますが、Fluxが解決する問題は基本的にオンメモリの上のやり取りなのでここでいうRepositoryパターンで扱うのは APIまたはDBなどとの処理になります。「APIからデータを取得し、DBに保存する」などの責務を追うことになります。

DomainStore

ドメインオブジェクトに対応したデータを格納するオンメモリのデータストアとして機能します。依然としてドメインロジックは持つことになるのですが、以前の状態よりは責務がスッキリしたかと思います。

PresenterStore

ある興味領域の状態を管理します。またDomainStoreの状態を監視しており、その状態変更に応じて状態を変更します。

またPresenterStoreはあるプレゼンテーション層に紐付いたものなので、プレゼンテーション層のライフサイクルにおいて破棄が可能です。 そのため、DomainStoreをシングルトンにすべきかは定めていません。DomainStoreがPresenterStoreによって管理されている場合は PresenterStoreの責任でDomainStoreに保存したデータも破棄することが可能です。

まとめ

  • FluxをDDDの視点で見たときの各レイヤーの関係性を整理した
  • FluxのStoreを構造化してみた

最後に

Flux実装やVirtualDOMの登場でフロントエンド人口は増えてきて嬉しいのですが、ちゃんとアーキテクチャについて議論できる場は減ったように感じます。(フレームワークやライブラリの話になりがち) なので、このタイミングで今までのアーキテクチャの変遷を整理するのは次なる発展のためにも必要なことだと思いました。

またここでは詳しく話しませんでしたが、他のレイヤードアーキテクチャやCQRS、EventSourcingなんかについてもいつか紹介したいと思います。

明日はmasuidriveによる「React + Rails」の話です。お楽しみに!

クローゼットがいっぱいになったので壁にハンガー掛けを作ってみた – @masuidrive blog

DIYについての話でした(・∀・)

参考資料

DDD, CQRS, EventSourcing

Flux and DDD

Layered Architecture