Erlang開発者の間で知られているように:Erlang開発者だけが正しく「生きる」方法を知っている そして他のみんなは「生きている」-間違っている 。 この事実に異議を唱えることなく、 Otplikeライブラリーを使用したClojure Erlangスタイルのアプリケーションの例を示します。
記事を理解するために、読者はClojureの基本の知識を必要とするかもしれません (まだClojureを知らない人がいますか?...) また、Erlang / OTPの基本原則(プロセス、メッセージの送信、 gen_serverおよびスーパーバイザーの動作)。 他のすべてに対処するために、Clojureの平均的な開発者は、サンプル 、REPL、およびタンバリンを含むコードに必要なものをすべて備えています。
なぜClojure
実際、「なぜClojure」なのかという質問には多くの答えがあります。 お気に入りは次のとおりです。
1番 Clojureは非常に効果的なプロトタイピング言語です。 Javaと比較して、Clojureでのモックアプリケーションの作成は非常に簡単です。データモデルを開発し、それらを組み立てるのは非常に簡単です。
2番 Clojureでは、アプリケーションのテストは非常に簡単です。REPL+レイアウトの容易さはここで決定します。 アプリケーションのテストケースが何であれ、目的のケースをテストするコンテキストを単純に構築するだけで十分です。
最初の2つのパラグラフは、アプリケーションの開発とサポート(指定された条件を満たす)を2回ごとに高速化します。
番号3。 ClojureはJava / JVMと完全に相互運用可能です。 これは、特に、Clojureアプリケーションでクラスを使用し、Clojureアプリケーションをクラスとしてエクスポートできることを意味します(たとえば、 clojureライブラリーをJavaアプリケーションに統合する )。 これは、JVMに蓄積されたすべてのヒューマンコードがClojureアプリケーションで利用できることも意味します。 これは、Clojure言語がJVMの開発に代わるものではなく、JVMの開発に反するものではなく、JVMへの追加(非常に重要な追加、私が言いたい)であることを意味します。
そのため、Clojureを使用すると、JVMの「人類の遺産」のどこにでもアクセスでき、テストするのに便利であると述べました。 さて、なぜClojureはすべて同じですか...
番号4。 Clojureは、複雑なことを簡単にするために開発された言語であり、私たちの意見では、言語の基本的なアイデアを定式化したRich Hickeyの経験と才能のおかげで実現しました(たとえば、ここで読むことができます: Clojureを学ぶ理由は? )
まあ、個人的に、私の好きな理由....
5番 Clojureでのプログラミングは楽しいです。 「生き生き」、面白くてストレスがありません。 プロジェクトを読み込んで、コードを読んで、考えて、書いて、 ユニコーンを出荷 結果を生成します。
ClojureでErlang / OTPを使用する理由
最後のセクションで、Clojureは「銀の弾丸」であることがわかりました。
ただし、マルチスレッドアプリケーションの時代の産業用プログラミングでは、便利でクールな言語に加えて、これらのマルチスレッドアプリケーション自体を開発するための実用的な方法論が必要です。
現在、Clojure上のマルチスレッドアプリケーションの標準ソリューションはcore.async
ライブラリです(たとえば、 core.asyncでマルチスレッドを準備する )。 ただし、実際の経験から、ライブラリ自体は優れていますが、実際には、より高いレベルの「ビルディングブロック」が必要であることがわかります。
そして、幸せなErlang開発者と彼らの「魅力」に戻ります。 Erlang / OTPは、おそらく他の言語とは違って、マルチスレッドアプリケーションの開発に多大な経験を積んできました。 Otplikeライブラリを使用してErlang / OTPの基本的なアイデアを実装すると、次のようになります。
- Erlangに似たプロセス、プロセス間のメッセージング、プロセス間のリンク。
- タイプ
supervisor
OTPプロセスの実装(supervisor behaviour
):設定に応じて、他のプロセスを監視し、それらをオーバーロードするプロセス。 -
gen_server
タイプのプロセスのOTP実装(gen_server behaviour
):マイクロサービスを実装するプロセス。 - タイマーによるプロセスの開始( スケジューリング )。
そして、根拠がないように、引用しましょう...
最小限のマイクロサービスアプリケーションの例
ToDoリストのサービス
次のコマンドを受け入れるTODOタスクリストのマイクロサービスを作成します。
- TODOエンティティの
create-todo [params] -> [:ok todo] | [:error reason]
:create-todo [params] -> [:ok todo] | [:error reason]
create-todo [params] -> [:ok todo] | [:error reason]
- IDでtodoを返す:
find-todo-by-id [id] -> [:ok todo] | [:error reason]
find-todo-by-id [id] -> [:ok todo] | [:error reason]
- todoの終了:
terminate-todo [id] -> [:ok updated_todo] | [:error reason]
terminate-todo [id] -> [:ok updated_todo] | [:error reason]
- delete todo:
delete-todo [id] -> [:ok nil] | [:error reason]
delete-todo [id] -> [:ok nil] | [:error reason]
- アクティブな仕事のリストを返し
enumerate-active-todos [] -> [:ok todos] | [:error reason]
:enumerate-active-todos [] -> [:ok todos] | [:error reason]
enumerate-active-todos [] -> [:ok todos] | [:error reason]
奇妙なことに、TODOサービスのパブリックAPIはこの説明から直接続きます(完全なコード例は、GitHubのこちらにあります )。
(defn create-todo [params] (call* [:create-todo params])) (defn find-todo-by-id [id] (call* [:find-todo-by-id id])) (defn terminate-todo [id] (call* [:terminate-todo id])) (defn delete-todo [id] (call* [:delete-todo id])) (defn enumerate-active-todos [] (call* :enumerate-active-todos))
call*
呼び出すことは、単にサービスにメッセージを送信することを意味します。
gen_server
サービスのメッセージ処理の本質は、これらのすべてのメッセージが順番に処理されstate
サービスのstate
値をあるメッセージ処理から別のメッセージ処理に渡すことです。 多くのプロセスがリクエストを並行してサービスに送信しても、これらのリクエストはすべて順番に実行されるため、これによりstate
の一貫性が損なわれることはありません。 実際には、これにより、サービスライフサイクルの開発が簡素化されます。
Otplikeの開発では、OTP コールバックになじみのある実装用にgen_server
サービスを利用できます 。
-
state
サービス初期化ハンドラー:init [args] -> [:ok initial_state]
- クライアントプロセスに結果を返すメッセージのハンドラ(同期メッセージ):
handle-call [message from state] -> [:reply reply updated_state] | [:noreply updated_state]
handle-call [message from state] -> [:reply reply updated_state] | [:noreply updated_state]
- クライアントプロセスに結果がないメッセージハンドラー(非同期メッセージ):
handle-cast [message state] -> [:noreply updated_state]
- システムメッセージハンドラー:
handle-info [message state] -> [:noreply updated_state]
- サービス完了ハンドラ:
terminate [reason state] -> nil
REPLでサービスをオーバーロードするための高度なスーパーバイザーの例
TODOサービスを実装し、REPLから新しいプロセスで起動したとします。 その後、このサービスのコードに変更を加え、テストするために変更したコードを実行したいと思います。 これをどうやってやるの?
1つの解決策は、古いコードでプロセスを強制終了し、新しいプロセスで新しいコードを実行することです。
ただし、これらのサービスの多くを持つことができ、互いに依存することができます。 さらに、サービスを起動する順序も重要になる場合があります。
したがって、元々OTPからの別の急進的な決定があります。私たちのサービスを監視し、それらをオーバーロードできるスーパーバイザープロセスが必要です。
さらに、スーパーバイザー自身をオーバーロードできるようにしたいので、REPLからアプリケーションを既知の初期状態にすることができます。
これらの要件について、Otplikeの次のコードが取得されました。
;;;;;;;;;;;;;;;;;;;;;;;;; supervision-tree (defn- app-sup [_config] [:ok [{:strategy :one-for-one} [{:id :todo-server :start [todo-server/start-link [{}]]}]]]) ;;;;;;;;;;;;;;;;;;;;;;;;; boot-proc (defn- start-app-sup-link [config] (supervisor/start-link :app-sup app-sup [config])) (defn- start-boot-sup-link [config] (supervisor/start-link :boot-sup (fn [cfg] [:ok [{:strategy :one-for-all} [{:id :app-sup :start [start-app-sup-link [cfg]]}]]]) [config])) (defn start [] (if-let [pid (process/whereis :boot-proc)] (log/info "already started" pid) (let [config (config/get-config)] (process/spawn-opt (process/proc-fn [] (match (start-boot-sup-link config) [:ok pid] (loop [] (process/receive! :restart (do (log/info "------------------- RESTARTING -------------------") (supervisor/terminate-child pid :app-sup) (log/info "--------------------------------------------------") (supervisor/restart-child pid :app-sup) (recur)) :stop (process/exit :normal))) [:error reason] (log/error "cannot start root supervisor: " {:reason reason}))) {:register :boot-proc})))) (defn stop [] (if-let [pid (process/whereis :boot-proc)] (process/! pid :stop) (log/info "already stopped"))) (defn restart [] (if-let [pid (process/whereis :boot-proc)] (process/! pid :restart) (start)))
app-sup
関数では、メインスーパーバイザーの子プロセスをリストします。
そして、残りのコードはスーパーバイザーを再起動するための回避策です。
そして最後に...
テスト中
REPLに進み、TODOサービスとアプリケーションの再起動がどのように機能するかを確認します。
プロジェクトルートからコンソールからREPLを開始します。
lein repl
アプリケーションを開始します。
erl-like-app.server=> (erl-like-app.server/start) <proc1@1> 18-05-11 14:29:24 INFO - todo server initialized
TODOペアを作成し、最初のTODOに完了マークを付けます。
erl-like-app.server=> (erl-like-app.todo.todo-server/create-todo {:title "task #1", :description "create task #2"}) [:ok {:title "task #1", :description "create task #2", :id "1", :created 1526049427586, :updated 1526049427586, :status :active}] erl-like-app.server=> (erl-like-app.todo.todo-server/create-todo {:title "task #2"}) [:ok {:title "task #2", :id "2", :created 1526049434985, :updated 1526049434985, :status :active}] erl-like-app.server=> (erl-like-app.todo.todo-server/terminate-todo "1") [:ok {:title "task #1", :description "create task #2", :id "1", :created 1526049427586, :updated 1526049443912, :status :terminated}]
アクティブなTODOとは:
erl-like-app.server=> (erl-like-app.todo.todo-server/enumerate-active-todos) [:ok ({:title "task #2", :id "2", :created 1526049434985, :updated 1526049434985, :status :active})]
state
サービスの価値は何ですか:
erl-like-app.server=> (erl-like-app.todo.todo-server/get-state) {:counter 2, :db {"1" {:title "task #1", :description "create task #2", :id "1", :created 1526049427586, :updated 1526049443912, :status :terminated}, "2" {:title "task #2", :id "2", :created 1526049434985, :updated 1526049434985, :status :active}}}
アプリケーションをリロードします。
erl-like-app.server=> (erl-like-app.server/restart) true 18-05-11 14:30:28 INFO - ------------------- RESTARTING ------------------- 18-05-11 14:30:28 INFO - todo server stopped 18-05-11 14:30:28 INFO - -------------------------------------------------- 18-05-11 14:30:28 INFO - todo server initialized
state
サービスの価値は何ですか:
erl-like-app.server=> (erl-like-app.todo.todo-server/get-state) {:counter 0, :db {}}
まとめ
そして、結果は何ですか:
- ErlangのClojureへの扉を開くOtplikeライブラリがあります。
- Otplikeへの扉を開くアプリケーションの例があります 。
この「ドア」が必要かどうかはわかりませんが、私の経験から言えば、この「ドア」の背後でクールなコードが得られていると言えます(つまり、効果的でシンプルなことです)。
良いコーディング!