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





まとめ



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









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







良いコーディング!










All Articles