ClojureアプリケーションでのErlangのようなマイクロサービス:簡単です

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の基本的なアイデアを実装すると、次のようになります。



そして、根拠がないように、引用しましょう...


最小限のマイクロサービスアプリケーションの例


ToDoリストのサービス


次のコマンドを受け入れるTODOタスクリストのマイクロサービスを作成します。


  1. TODOエンティティのcreate-todo [params] -> [:ok todo] | [:error reason]create-todo [params] -> [:ok todo] | [:error reason] create-todo [params] -> [:ok todo] | [:error reason]
  2. IDでtodoを返す: find-todo-by-id [id] -> [:ok todo] | [:error reason] find-todo-by-id [id] -> [:ok todo] | [:error reason]
  3. todoの終了: terminate-todo [id] -> [:ok updated_todo] | [:error reason] terminate-todo [id] -> [:ok updated_todo] | [:error reason]
  4. delete todo: delete-todo [id] -> [:ok nil] | [:error reason] delete-todo [id] -> [:ok nil] | [:error reason]
  5. アクティブな仕事のリストを返し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サービスを利用できます


  1. stateサービス初期化ハンドラー: init [args] -> [:ok initial_state]
  2. クライアントプロセスに結果を返すメッセージのハンドラ(同期メッセージ): handle-call [message from state] -> [:reply reply updated_state] | [:noreply updated_state] handle-call [message from state] -> [:reply reply updated_state] | [:noreply updated_state]
  3. クライアントプロセスに結果がないメッセージハンドラー(非同期メッセージ): handle-cast [message state] -> [:noreply updated_state]
  4. システムメッセージハンドラー: handle-info [message state] -> [:noreply updated_state]
  5. サービス完了ハンドラ: 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))) 

完全なコードは、GitHubでここで表示できます。


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 {}} 

まとめ


そして、結果は何ですか:



この「ドア」が必要かどうかはわかりませんが、私の経験から言えば、この「ドア」の背後でクールなコードが得られていると言えます(つまり、効果的でシンプルなことです)。


良いコーディング!




Source: https://habr.com/ru/post/J354984/


All Articles