私が歌川です

@utgwkk が書いている

AWS Lambda + DynamoDBでzatsu_monitorをサーバーレスで動かす

モチベーション

zatsu_monitorはシンプルなURL監視ツールです。crontab等で定期的に指定したURLにリクエストを送信し、ステータスコードに変化があれば通知してくれます。詳しくは作者の id:sue445 氏の記事をご覧ください。

sue445.hatenablog.com

手軽にURLを監視したいけどサーバーの管理はしたくない、というモチベーションで実装を進めることにしました。

方針

ここではAWSを使うことにします。GCPなど他のクラウドサービスにも対応するコンポーネントがあると思うので、適宜読み替えたら再現できると思います。

サーバーレスといえばLambda、ということでzatsu_monitorをLambdaで定期実行する大方針を固めます。単に実行するだけなら main 関数をちょっと差し替えればできます。ところで、zatsu_monitorはファイルシステム上に状態*1を持っているので、これをLambdaでどう扱うか考えなければいけません。

ぱっと思いつく方針としては以下があります。

  • LambdaからEFSをマウントする
  • DynamoDBに状態を持つように修正する

コードを修正する量を考えると、LambdaからEFSをマウントするほうが手間が少ないかもしれません。しかし、EFSの容量や料金を気にするよりは、もっとサーバーレスに寄せたストレージに書き込む方がよさそう*2、と判断してDynamoDBを使うようにコードを書き換えることにしました。

完成品

forkしたリポジトリの dynamodb ブランチに実装をpushしています。大きなみどころは以下の2つです。

DynamoDBのテーブルを作って、LambdaのトリガーにEventBridge (CloudWatch Events)を設定し、5分おきに関数を実行するようにして完成です。テーブル構成は本当にシンプルなkey-value storeになっています。

改善したい点

Lambda関数のzipに設定ファイルを同梱してデプロイする形なので、zatsu_monitorの設定を修正する場合はデプロイが必要になります。また、LambdaのWebコンソールから設定内容を確認することができません。

おわりに

サーバーレス元年ということで、便利なツールがサーバーレス環境で動くように改造するのもよいかもしれません。

*1:前回リクエスト時のステータスコード

*2:DynamoDBを触ってみたかった、という裏テーマもある

Pythonソースコードの文法チェックを行うテストを書く

src ディレクトリ以下のPythonソースコードの文法チェックを行いたい。テストとして回せるとよさそう。

pytestを使って書くならこういう感じになりそう。Python 2.7対応のためにglob2ライブラリを使っているけど、Python 3.5以降なら標準のglobモジュールで事足りる。

py_compileはPythonソースコードをバイトコードにコンパイルする組み込みモジュールだけど、コンパイルする際に文法エラーがあったらコンパイル失敗するので、これを活用できないだろうか、というアイデア。

from glob2 import iglob
import py_compile
import pytest

python_files = iglob('src/**/*.py', recursive=True)

@pytest.mark.parametrize("path", python_files)
def test_syntax(path):
    try:
        py_compile.compile(path, doraise=True)
    except py_compile.PyCompileError as ex:
        pytest.fail(ex.msg)

ちょっと試した範囲ではうまく動作していそう。

github.com

gyazo.com

背景

Pythonソースコードの文法エラーがあったら、GitHub Actionsで落ちたテストをアノテーションするpytestプラグインでアノテーションしたいという話題があった。

github.com

Python 2.7対応を考える場合は文法の互換性に気をつける必要があるので、欲求はよくわかる。一方で、文法エラーがあるモジュールを読み込んだ時点でSyntaxError例外を送出してテストが終了するので、単体ではアノテーションするのが難しいと思う。

ところで、文法エラーがあったら落ちるテストを用意できればプラグインの実装を変えずにリーズナブルに実現できるのではないか、と思って書いた。

25歳になった

7/28で25歳になりました。

2021年には退学をしました。人生の岐路ともいえるのですが、決まってからの一連の手続きは高速に済んでいきました。こうやって突き進んでいくんですね。

退学を決めた頃のプライベートな日記を見返してみると、あまりにスピード感がありすぎるとビビっている様子が伺えます。人生は決断の連続って感じですね。

25歳になるということで、25歳のアイドルのことを思い出しました。25歳のモデルケースは他にもある気がするけど真っ先に思い付いたので仕方ないです。温泉に行きたいし健康的な飲酒をやりたい。ブログには書いてなかったと思うけど、健康診断の結果のみどころは特にありませんでした。

blog.utgw.net


とりあえず欲しい本のウィッシュリストを貼っておきます。ほかにもオススメの本がありましたらウィッシュリスト外から送ってください。

読書カテゴリScrapboxにある本は、読んだことがあるか、もしくは持っています。

日常を取り戻した暁には飲みに行きましょう。


あと、Kindleで買えるオススメの百合漫画があったら教えてください。

TypeScript + JSXでrenderするコンポーネントに型引数を書ける

子ネタだしタイトルが全てだけど、おもしろかったので共有します。

たとえば、react-router (react-router-dom) v5 の Link コンポーネントto propには、文字列・オブジェクト・関数を渡すことができる。

ここで、react-router-domの型定義を見ると、<Link> コンポーネントはジェネリックな関数・インタフェースとして定義されていることが分かる。

これらを踏まえて以下のようなコードを書いてみると、コンパイルが通るし動作することが分かる。renderしたいコンポーネントに型引数を書くことができる。

type State = ...;

export const WrappedLink = () =>
  <Link<State> to={(location) => ...} />

こすると to propの関数の location 引数の型が Location<State> に推論されて便利だけど、見た目はおもしろい。関数呼び出しだと思うと、ここに型引数が書けるのはそういうものかとも思う。

2021/7/26 12:54 追記: コンポーネントのrender時に「型注釈」を渡す、と書いていたけど正しくは「型引数」だったので修正しました。

艦これリキュールを飲んで連休ラストスパートに差しかかりつつある。


catatsuyさんの社内ISUCONで26万点を取った。3年前にも解いたことがあるけどそのときよりも遥かに点数が高い。解いたことがあるとはいえ手数が増えたということだと思う。小手先テクニックも覚えている……。

blog.utgw.net

2021/7/25 追記: 27万点まで伸びた。


www.youtube.com

7月の終わりが近づいている、ということを意識した。誕生日まであと4日。

イベントハンドラを設定した要素内にreact-modalのモーダルを置くとイベントハンドラが反応する

tl;dr

イベントハンドラを設定した要素内にreact-modalのモーダルを置かないようにするのが手っ取り早そう。

イベントハンドラを設定したコンポーネント

以下の Clickable コンポーネントは、divに click イベントのハンドラを設定しており、かつchildrenを取るコンポーネントである。divをクリックするとコンソールにログを出力する。

import { FC, MouseEventHandler, useCallback } from "react";

export const Clickable: FC = ({ children }) => {
  const handleMouseDown: MouseEventHandler = useCallback((e) => {
    console.log(`clicked!!!! ${new Date()}`);
  }, []);

  return (
    <div onMouseDown={handleMouseDown}>
      {children}
    </div>
  );
};

モーダル、外に置くか? 中に置くか?

react-modalのモーダルを <Clickable> の外に置くか、それとも中に置くか、について考える。

外に置く

モーダルを <Clickable> の外に置くとこういう感じになる。モーダルを開くボタンだけが <Clickable> の中にある。とくに気にかけることもない。

<div>
  <h2>along with modal</h2>
  <Clickable>
    This area is clickable!!!!!
    <p>
      <button onClick={() => setIsOpen(true)}>open modal</button>
    </p>
  </Clickable>
  <Modal isOpen={isOpen} onRequestClose={handleRequestClose}>
    <div>
      <h2>Modal</h2>
      <button onClick={handleRequestClose}>close</button>
    </div>
  </Modal>
</div>

中に置く

一方で、モーダルを <Clickable> の中に置くとどうなるか? この場合もモーダルを開くことはできるし、モーダルが前面に出てくるのだが、モーダル内をクリックすると <Clickable> のイベントハンドラが反応してしまう。これは多くの場合は望ましい挙動ではないと思う。

<div>
  <h2>contains modal</h2>
  <Clickable>
    This area is clickable!!!!!
    <p>
      <button onClick={() => setIsOpen(true)}>open modal</button>
    </p>
    <Modal isOpen={isOpen} onRequestClose={handleRequestClose}>
      <div>
        <h2>Modal</h2>
        <button onClick={handleRequestClose}>close</button>
      </div>
    </Modal>
  </Clickable>
</div>

どうするか

モーダル内で、モーダルを設置した要素の親のイベントハンドラが呼ばれてほしくないなら、モーダルを外に出したほうがよい。

感想

普通はイベントハンドラを設定した要素の子にモーダルを設置することはないかもしれないけど、モーダルを動的に生成する場合に油断すると引っかかると思う。実際にこの現象に遭遇して、DOM treeの上ではreact-modalのモーダルは完全にroot要素の外にあるけど、Reactが管理するtreeの上では必ずしもそうなっていない*1のがデバッグを難しくしていた。CSSの pointer-events プロパティで解決できるかもしれない、と思っていろいろ試してみたけど解決できなかった。

サンプルコード

github.com

2021/8/2 追記

Reactのポータルのドキュメントを読んでいたところ、まさにこの現象に該当することがドキュメントに書いてあった。

ポータルは DOM ツリーのどこにでも存在できますが、他のあらゆる点では通常の React の子要素と変わらずに振る舞います。コンテクスト (context) のような機能は、たとえ子要素がポータルであろうと全く同じように動きます。というのも、DOM ツリー上の位置にかかわらず、ポータルは依然として React のツリー内にいるからです。

これにはイベントのバブリングも含まれます。ポータルの内部で発火したイベントは React のツリー内の祖先へと伝播します。たとえそれが DOM ツリー上では祖先でなくともです。

ja.reactjs.org

ということは、react-modalのオーバーレイに対して event.stopPropagation() するだけのイベントハンドラを設定しても回避できるかもしれない。

*1:React Developer Toolsで確認できる