Mastodonについてのあれこれ

Mastodonについての話題がここ数週間の内に破竹の勢いでかけめぐっています。ここまでの勢いというのはTwitterができたばかりの2007年のころが思い起こされ、とてもなつかしい気持になっています。

Mastodonとの出会い

わたしがMastodonのアカウントを作ったのは日本で話題になり初めた四月十一日の夜遅くであり、少し遅いです。前職の同僚と飲んでいる際に話題となって、その場でmastodon.cloudにアカウントを作りました。その後、日本人が多くいるという話を聞いたmastodon.nil.nu (現 mstdn.jp) にアカウントを移して、mstdn.jpでの二度のデータベース削除の末にPawooに落ち着きました。

結果としてわたしとMastodonアカウントは@ykzts@pawoo.net@ykzts@mstdn.jp@ykzts@mastodon.cloudの三つに加えて、検証用に自分で立てたインスタンスのアカウントである@ykzts@omanko.pornの合計 四つです。少し作り過ぎてしまったかな? と思わなくもないので、これ以上のアカウントを取得する予定はありません。ほかのインスタンスで「@ykzts」というユーザー名のアカウントが存在していても、それはわたしではない別の人でしょう。

Pawooの運営元であるピクシブ株式会社は何年も前からイラスト投稿サイト「pixiv」を運営しています。そうしたつながりからか、Pawooには絵を描かれる方が多く参加しています。当然 Pawooにおいてもイラストを投稿されていて、ローカルタイムラインにおけるイラストの比率は非常に高くなっています。性的な話題も多くあり、人を選ぶようなインスタンスではあるかと思います。

ですが、だからこそわたしにとっては都合が良く、そしてとても居心地の良い環境です。わたしはこうした場所を待ち望んでいたのかもしれません。

Mastodonインスタンスの運用

さて、MastodonのバックエンドにはRuby on Rails (Rails) でフロントエンドにはReactが使われています。この両フレームワークはわたしがこれまで公私ともに使ってきたものであり、わたしの技術スタックと見事に合致するものでした。両フレームワークを用いたウェブアプリケーションの運用についての知見もありますし、また実装についての知識もないわけではありません。

技術的にある程度は触れそうだと思い、Mastodonのアカウントを最初に取得してからちょうど一週間後である四月十八日にさくらのVPSの契約をしてMastodonのインスタンスを作りました。勢いで取ってしまったomanko.pornというあまりにもはずかしい名前のドメイン名での運用になっています。

現在は登録の受け付けをしていませんが、ルール決めや説明文についての記載をおえたら登録の受け付けを開始しようかと考えています。これまでわたしが培ってきた知識を動員したものになっているのでさくらのVPS 1台で動かしているにしては高速に動かせるのではないかと思っています。このはずかしいドメイン名のインスタンスにどれほどの人が登録しようと思ってくださるかは未知数ですが、それなりの人数は抱えることができるのではないかと期待しています。

omanko.pornのインフラ面

サラリーマンであるわたしは平日の昼間に趣味で運用しているサーバーの面倒を見ることは難しい状況です。そのため、運用における手間を最小限に留められるようにDockerDocker Composeを使っています。使っているdocker-compose.ymlはGitHubのリポジトリーにNginxの設定ファイルと一緒にまとめてあるので、ある程度の参考にすることができるのではないかと思います。ただ、わたしのインスタンスに依存する記述も多いのでそのまま使うことはできないでしょう。あくまで参考にできるだけでしょう。

またMackerelのプラグインとしてmackerel-plugin-mastodonも作りました。これはMastodonインスタンスの「サーバー情報」のページから確認できるユーザー数、投稿されたトゥートの数、接続しているインスタンスの数を定期的に取得して、Mackerelでグラフにプロットしてもらえるようになります。Mackerelを使うことによって、ユーザー数が閾値に達しそうな時にアラートを鳴らすことができるので便利なのではないでしょうか。

Mastodonへのコントリビュート

前置きが長くなりました。ここからが本題です。

Mastodonはまだ生まれたばかりです。最初のコミットがされてから、まだ一年も経っていません。まだまだ成長の余地が多くあります。

「Mastodonインスタンス運用」の節でも書いたように、Mastodonで使われている技術はわたしの技術スタックと合致しています。MastodonはGNU Affero General Public License Version 3 (AGPL) でオープンソースソフトウェアとして公開されていることもあり、わたしの微力でも開発に少しでも役立てられるのではないかと思い、Mastodonの上流に対するコントリビュートも少しずつしています。

この記事を書いている時点で、32個のPull Requestsを送り、ありがたいことにその内 29個をmergeしていただいています。日本語への翻訳や細かいバグの修正が主ですが、少しでも貢献ができているのであればうれしく思っています。

Mastodonの原作者 (一番初めにコミットをした人) であるGargron氏は現在 Patreonで寄付者を募り、フルタイムでMastodoonの開発を行っています。日本からでも「Gargron is creating open-source software | Patreon」から簡単にGargron氏のパトロンになることができるので、比較的貢献はしやすいかと思います。

ここ数箇月で大きなお金を使う事情が何度かあったため、わたしは残念ながらGargron氏に対して月に10USDの貢献しかできていません。その分、開発での貢献ができれば……と考えています。

Mastodonに対して少しでもお世話になっている、もしくはなりそうだと思われた方は「Gargron is creating open-source software | Patreon」からGargron氏のパトロンになると良いでしょう。リターンとしてGargron氏を含めたMastodonの開発者やMastodonのインスタンスを管理している人たちが集まるチャット (Discord) への招待もあります。パトロンになっても損をする要素はないかと思います。

参考までにわたしのメールアドレスはykzts@desire.shです。Gargron氏のパトロンになった上で、余剰のお金があるのであればわたしにも寄付としてAmazonギフト券をいただけたらな、と思います。

Mastodonインスタンスの運用やMastodon開発における検証をするためのコストといったものが少なからずかかってしまいそうなので、金銭的な対価が少しでもあるととてもうれしく思います。わたしからのリターンとしてはMastodonの開発や運用における知見を共有することといったことしかできませんが、よろしければよろしくお願いいたします。

優秀なエンジニア

わたし自身が優秀なエンジニア (ソフトウェアエンジニア) であると他者から評価される、されないといったことは正直 興味を抱くところはない。評価とかを気にすることなく自身が興味を抱いた技術についてひたすら知識を深めていくだけである。

だが、自分では自分のことを優秀だと思っているし、優秀だと言うようにしている。

雇ってもらおうと思い、企業の面接に行くときは当然 自己のPRをする。もちろん嘘を吐くことはないし、そもそも嘘は吐きたくない。なのでやってきたことや、やりたいことを正直に言うだけではある。そこで自分が優秀であり採用することによって、どのような利を得られるかと説明する。

そのわたしの説明を聴いて (「聞いて」ではないはずだ) 納得をした上で採用してくれた人がいる以上、わたしがわたしのことを優秀ではないと言うのはその人たちに対する裏切りでしかない。

そんな不誠実は、わたしはしたくない。わたしは誠実であるために、自分が優秀であると言い続ける。

とは言うが、わたしを雇っても凡百のエンジニア十人分の活躍ができるというわけではない。一人で十人分のコミットをすることはできず、そしてするつもりもない。だが十種類の人間を雇わなければできないような幅広い職域の作業を一人でできる。

決して手が速いわけではない。知識が広く、ただ単純に汎用性が高いというだけ。

それが優秀であるかどうかは異論があると思う。だが、わたしはわたしが誠実であり続けられるように自分を優秀と言う。言わなければならない。

「それ」を必要とするところに行くというだけの話である。

BaberuTV 進捗報告

BaberuTV 進捗報告

少し前から作っているBaberuTVだが、順調に開発が進行している。

わたしの技術検証という側面が強い都合上、機能の追加のペースはどうしてもゆるやかになってしまっている。だがMaterial-UIを使うことによって見た目に関してもそれなりに見られるものになってきたのではないかという自負がある。

DockerとDocker Composeを使うことによって、手元の環境をさして汚すことなく開発環境の構築を行えるようになった。現在は静的ページのみなのでGitHub Pagesにデプロイしているが、将来的にはサーバーサイドレンダリングをすることを視野にいれている。そうした場合はEC2やHerokuといった場にデプロイすることも考えなければならないので、今のうちからDockerで環境の構築が簡単にできるようにしておけて良かった。

Go言語をおぼえる前に開発環境構築に手間をかけたくなく、Dockerの勉強をはじめたのだが、いつの間にかGo言語よりもDockerのほうに夢中になってしまっていた。Go言語の習熟ができていないというわけではないのだが、しかしDockerのほうに割いた時間のほうがはるかに多い。そしてDockerのほうが楽しめてしまっている。これは正直に言って予想外であった。

また現在はClosed Betaで語れることは少ないがCircle CI 2.0も良い具合に使えている。問題が全くないというわけではないが、サポートの対応もそれなりに早く好印象である。はやく正式に提供開始されて欲しいものである。

GitHubのリポジトリーのドキュメントもそれなりに整備した。開発環境の構築もDocker Composeのおかげでしやすくなったのではないだろうかと思う。

BaberuTVは今月中にトップページをランディングページの体にして、v2.0.0として公開しようと考えている。今は開発環境やドキュメントの整備、そしてユーザーの目に触れる部分の調整に力を入れている。機能拡充については四月以降に進めていきたい。

「技術力」とはいったいなんなのか

ここ数年、ずっと自問自答している。

W3Cの勧告やRFCといった技術仕様や、言語やツールのREADMEを読むことによって得られる知識を持っているだけで「技術力」があると言えるのか、と。

多くの技術は、「読む」という行為によって容易に得ることができる。極論を言ってしまえば、読むことさえできれば誰でも「技術力」を得られるというのがわたしの持論である。

「読む」という行為に障壁を感じたことが薄く、「技術力」がないと自らを称す人のことは正直に言ってしまうと理解に苦しむ。「読む」だけでは技術が得られない、というのであればわたしは「技術力」がないということになってしまう。

また、わたしの「技術力」に対して一定の評価をして、雇用という形でそれ表現している人たちが存在する以上、自らの「技術力」がないと言うことはどうしても礼を失することになってしまうのではないかと考えてしまう。

「技術力」とは、いったいなんなのだろうか。

2017年の春から開発者として働き始める人が読むべき技術書

わたしがソフトウェア開発者として働くようになってからもう何年も経ちますが、通読した技術書は0です。技術書を読まなくてもソフトウェア開発者として働き続けることはできます。

この技術書を読まなければならないといったことを半ば強制的に言ってくる人の言葉には耳を貸さなくても大丈夫です。

もし、強く技術書を買うよう要求してくる人がいるのであればその人に本を買ってもらいましょう。そういうことができるのは若者の内だけです。

技術書を読めと言うのならば、そうした責任を持つべきです。とくに働き始めの人はまだお金も少なく、ほかの書籍と比べると高価な技術書を購入することは難しいのですから。

WebSocketでタイムラインを表示させるだけのウェブアプリケーション「tweet receiver」を作っている

tweet receiverというウェブアプリケーションを作りました。

Twitterでフォローしている人のツイートをUser streamsを介して取得し、それをWebSocket (RFC 6455) を経由させてリアルタイムに表示させるだけの機能しか有していません。ツイートを投稿することはできませんし、できるようにするつもりも今の時点ではありません。純粋に人のツイートを見るだけです。

User streamsを活用したウェブアプリケーションを別に開発していて、その検証のために簡単なものを作ろうと思って作り始めたものです。ですが、動かしてみたところ思ったよりも便利なので、もう少し見た目の体裁を整えて常用に耐えるものにしようと考えています。

import()を使ったHTTP/2時代のフロントエンド実装

import()を使ったHTTP/2時代のフロントエンド実装

この記事はhttp2 Advent Calendar 2016の二十三日目の記事である。

今年……2016年はHTTP/2が大いなる躍進を遂げた年であったと感じる。

Amazon CloudFrontも今年の九月にHTTP/2の対応が追加された (Amazon CloudFront now supports HTTP/2)。またFastlyでも十一月から一般利用が可能となった (HTTP/2 is now in General Availability)。

これまではNginxApache HTTP ServerなどのHTTP/2に対応したHTTPサーバーをフロントとして自身で運用しなければならなかった。Contents Delivery Network (CDN) サービスの恩恵を享受できず、また運用の手間をかける必要もあった。

しかし今年 多くのCDNサービスがHTTP/2に対応したため、手間をかけることなく誰もがHTTP/2の恩恵を気軽に享受できるようになった。今後HTTP/2で配信されるウェブページが増えていくことであろう。

HTTP/2が策定される以前のHTTPの規格であるHTTP/1.1では同一のホスト名に対して同時にリクエストできる数には制限がある。そのため、複数のスクリプトファイルが埋め込まれたウェブページはページ全体の読み込みが完了するまでに時間がかかってしまっていた。それを解決するために複数のスクリプトファイルをwebpackBrowserifyといったバンドルツールを用いて結合させるといったことが、半ば当たり前のこととなっていた。

HTTP/2ではリクエストとレスポンスのストリームが多重化されている。並列して複数のリクエストを送信できるようになったため、これまで当たり前のことであったスクリプトファイルの結合の必要性は薄くなった。

// src/client.jsx

import React from 'react';
import ReactDOM from 'react-dom';

function render(element, container) {
  return new Promise((resolve, reject) => {
    try {
      ReactDOM.render(element, container, resolve);
    } catch (error) {
      reject(error);
    }
  });
}

async function main() {
  const App = await import('./components/App');
  const container = document.getElementById('root');
  try {
    await render(<App />, container);
  } catch (error) {
    // eslint-disable-next-line no-alert
    alert(error.message);
  }
}

main();

のようにimport()で別のスクリプトファイルを指定すると実行時にそのスクリプトファイルが読み込まれるようになる。import()Promiseを返すので、ECMAScript 2017から仕様に追加されるAsync Functionsとの相性が極めて良い。

import()はこの記事を執筆している時点 (2016年十二月二十三日) ではstage 3であり、正式な仕様となっているものではない。そして実装されているウェブブラウザーもまだ存在していない。しかしwebpack (v2.1.0-beta.28以降) を使うことによって、期待するような効果が得られる。

webpack単体でもimport()に対応しているが、Babelも一緒に使うのであればBabelで処理させるためにbabel-plugin-syntax-dynamic-importというBabelのプラグインを導入させなければならない。

またBabelはCommonJSに対応していないので、webpackから読み込んでも実体はそのまま返されない。defaultという名前のプロパティーを含むobjectが返されるため、(await import('./components/App)).defaultのような形で呼び出さなければならず、いささか煩雑である。これはbabel-plugin-add-module-exportsを使うことによって解決できる。babel-plugin-add-module-exportsはその名前の通り、module.exportsを追加するBabelのプラグインである。これによりwebpackの想定するCommonJSに対応する形へのトランスパイルが可能となる。

最終的にwebpackの設定 (webpack.config.js) は次のようになる。

// webpack.config.js

const BabiliPlugin = require('babili-webpack-plugin');
const HtmlPlugin = require('html-webpack-plugin');
const path = require('path');
const merge = require('webpack-merge');

const clientConfig = {
  devtool: 'source-map',
  entry: {
    main: path.join(__dirname, 'src', 'client.jsx'),
  },
  module: {
    rules: [
      {
        exclude: /node_modules/,
        options: {
          babelrc: false,
          plugins: [
            'add-module-exports',
            'syntax-dynamic-import',
          ],
          presets: [
            ['env', {
              debug: true,
              targets: {
                browsers: [
                  'Chrome &gt;= 55',
                ],
              },
            }],
            'react',
          ],
        },
        test: /\.jsx?$/,
        loader: 'babel-loader',
      },
    ],
  },
  output: {
    chunkFilename: '[id].[chunkhash].js',
    crossOriginLoading: 'anonymous',
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'build', 'public'),
    publicPath: '/',
  },
  plugins: [
    new HtmlPlugin({
      template: path.join(__dirname, 'src', 'templates', 'index.ejs'),
    }),
  ],
  resolve: {
    extensions: [
      '.js',
      '.jsx',
    ],
  },
};

module.exports = (env) => {
  switch (env) {
    case 'production':
      return merge(clientConfig, {
        plugins: [
          new BabiliPlugin(),
        ],
      });
    default:
      return merge(clientConfig, {
        output: {
          chunkFilename: '[id].js?[chunkhash]',
          filename: '[name].js?[chunkhash]',
        },
      });
  }
};

今回 書いたコードの全体はykzts-sandbox/try-dynamic-importにある。MITライセンスで公開しているので自由に使ってもらって構わない。

今後、HTTP/2での通信はより一般的なものになっていくと予想される。HTTP/1.1時代の常識のままでいては良くないだろう。フロントエンドに携わるソフトウェア開発者でも、HTTPについてある程度の知識と理解を持ち、よりユーザーの方向を向いた適切な開発を心がけて行きたい。