Closed5

CEL(Common Expression Language)を学ぶ

ぱんだぱんだ

最近CEL(Common Expression Language)という単語をよく目にして軽く調べたが記事とかあんまりない感じだったのでちゃんと調べてみる。

ぱんだぱんだ

採用例

mercariさんのgrpc federationでも採用されているそう

https://engineering.mercari.com/blog/entry/20240401-4f426bd460/

ProtobufのバリデーションができるprotovalidateでもCELは採用されている。

https://github.com/bufbuild/protovalidate

あとたまたま見かけた以下の記事でも管理画面でCELを入力して評価するWASMを組み込んだそうな

https://developers.cyberagent.co.jp/blog/archives/47580/

Google CloudのCertificate Authorityb Serviceのポリシーを書くのに
https://cloud.google.com/certificate-authority-service/docs/using-cel?hl=ja

k8sの何かしらの独自定義にも使われてるそう
https://zenn.dev/zoetro/scraps/30eabefe3545d4

Firestoreのセキュリティールールを書くのにも使われてそう
https://firebase.google.com/docs/rules/rules-language?hl=ja

ぱんだぱんだ

CELとは

既に以下のような記事がありますのでこちらを拝読しましょう。

https://christina04.hatenablog.com/entry/common-expression-language

特徴

  • Googelによって開発された式言語
  • 非チューリング完全言語
  • 軽量、高速
  • 型による安全性
  • 多数の組み込み関数のサポート
  • 拡張性(ユーザー定義関数やカスタム型を使って)
  • 執筆時点でGo, Java, C++などの言語向けのバインディングが提供されている

上記のような特徴からアクセス制御やポリシー定義などに使用されることが多い。また、protovalidateのようなスキーマの値を評価するようなユースケースに適している。

ユースケース

  • スキーマを定義できない柔軟なConfig
    • Firestoreのセキュリティールールやk8sなどの採用はこのユースケースなのかなと理解した
  • 非エンジニアでも記述しやすい
    • これはCyberさんの事例がそうなのかなと思った(違うかも)
  • Protobufと親和性が高い
    • これはgRPC Federationとprotovalidateが採用している理由

キーコンセプト

  • Parse, Check, Evaluateの3つのフェーズ
  • Parse/ChackのフェーズとEvaluateのフェーズが分離している
  • Parseでは評価式をAST(抽象構文木)に変換する
  • Checkでは式に含まれている変数や関数が宣言されているかチェックする
  • EvaluateでASTを使って入力値を評価する
  • ベストプラクティスとしてParseとCheckのフェーズはランタイムで実行するのではなく事前に登録してASTとして保存してくのがパフォーマンス的に良い

cel-go

cel-goの使い方などは上記のParse, Check, Evaluateの順でASTを使って入力値を評価するだけなのと元記事に全て書いてあるのでそちらを読んでください

ぱんだぱんだ

CELドキュメント

CELで何かを作る場合はチュートリアルをやると全体的な流れがわかりそう。

https://cel.dev/tutorials/cel-get-started-tutorial?hl=ja

今回はprotovalidateなどでいい感じにCELの評価式を書きたいというモチベーションなので素直にCELのリポジトリにあるドキュメントを読み込んでいく。

https://github.com/google/cel-spec/blob/master/doc/langdef.md#overview

変数

CELの変数は以下の型の変数を取る。CELは型安全的なことを書いた気がするが型付けは動的。

  • int
  • uint
  • double
  • bool
  • string
  • bytes
  • list
  • map(keyはint, uint, bool, string)
  • null_type
  • message names(Protobuf message)
  • type 型自体を表す型

CELからProtobuf、ProtobufからCELへの変換ができるがCELよりもProtobufの方が表現できることが多いため、CELからProtobufは問題ないがその逆は問題になるかもしれない。

ProtobufはJSONへのマッピングが定義されているためCELからJSONも問題なくできる。

三項演算子はある

マクロ

CELは関数とは別にマクロが存在する。

  • has(e.f)
  • e.all(x, p)
  • e.exists(x,p)
  • e.exists_one(x,p)
  • e.map(x,t)
  • e.map(x, p, t)
  • e.filter(x,p)

以下の演算子と関数を見ておけば評価式は書けそう

https://github.com/google/cel-spec/blob/master/doc/langdef.md#regular-expressions

このスクラップは3ヶ月前にクローズされました