diff --git a/404.html b/404.html index 7d3f330f9..f240e5ad6 100644 --- a/404.html +++ b/404.html @@ -18,8 +18,8 @@ - - + +
diff --git a/assets/js/141346fb.75fa4823.js b/assets/js/141346fb.75fa4823.js new file mode 100644 index 000000000..79da872e0 --- /dev/null +++ b/assets/js/141346fb.75fa4823.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[4098],{3910:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var o=s(4848),n=s(8453);const i={slug:"basics-of-workflow",authors:["mitchellh"],tags:["analysis","littlehorse"]},r="The Basics of Workflow",a={permalink:"/blog/basics-of-workflow",source:"@site/blog/2024-09-04-basics-of-workflows.md",title:"The Basics of Workflow",description:"LittleHorse Enterprises is a workflow engine company. But what is a workflow engine?",date:"2024-09-04T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:5.675,hasTruncateMarker:!0,authors:[{name:"Mitchell Henderson",title:"Principal Technical Architect",description:"Mitch is a software industry veteran experienced with Apache Kafka, Apache Cassandra, and systems modernization across a diverse set of industries including financial services, healthcare, technology, retail, and manufacturing. He prides himself in ensuring that all users of LittleHorse are successful.",page:{permalink:"/blog/authors/mitchellh"},socials:{github:"https://github.com/mitchell-h",linkedin:"https://www.linkedin.com/in/mitchellghenderson/"},imageURL:"https://avatars.githubusercontent.com/u/6223426",key:"mitchellh"}],frontMatter:{slug:"basics-of-workflow",authors:["mitchellh"],tags:["analysis","littlehorse"]},unlisted:!1,prevItem:{title:"Integration Patterns: Saga Transactions",permalink:"/blog/saga-pattern"},nextItem:{title:"Microservices and Workflow: A Match Made in Heaven",permalink:"/blog/microservices-and-workflow"}},l={authorsImageUrls:[void 0]},c=[{value:"Workflow Architecture",id:"workflow-architecture",level:2},{value:"Workflow Specifications",id:"workflow-specifications",level:3},{value:"Tasks and Task Workers",id:"tasks-and-task-workers",level:3},{value:"Workflow Clients",id:"workflow-clients",level:3},{value:"LittleHorse Use-Cases",id:"littlehorse-use-cases",level:2},{value:"Microservices",id:"microservices",level:3},{value:"Human-in-the-Loop",id:"human-in-the-loop",level:3},{value:"RAG and AI",id:"rag-and-ai",level:3},{value:"Legacy System Modernization",id:"legacy-system-modernization",level:3},{value:"API gateway",id:"api-gateway",level:3}];function h(e){const t={a:"a",admonition:"admonition",br:"br",code:"code",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,n.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.p,{children:"LittleHorse Enterprises is a workflow engine company. But what is a workflow engine?"}),"\n",(0,o.jsx)(t.p,{children:"It is a system that allows you to reliably execute a series of steps while being robust to technical failures (network outages, crashes) and business process failures. A step in a workflow can be calling a piece of code on a server, reaching out to an external API, waiting for a callback from a person or external system, or more."}),"\n",(0,o.jsxs)(t.p,{children:["A core challenge when automating a business process is ",(0,o.jsx)(t.strong,{children:"Failure and Exception Handling:"})," figuring out what to do when something doesn't happen, happens with an unexpected outcome, or plain simply fails. This is often difficult to reason about, leaving your applications vulnerable to uncaught exceptions, incomplete business workflows, or data loss."]}),"\n",(0,o.jsx)(t.p,{children:"A workflow engine standardizes how to throw an exception, where the exception is logged, and the logic around when/how to retry. This gives you peace of mind that once a workflow run is started, it will reliably complete."}),"\n",(0,o.jsx)(t.h2,{id:"workflow-architecture",children:"Workflow Architecture"}),"\n",(0,o.jsxs)(t.p,{children:["Any ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts",children:"workflow-driven application"})," has three components:"]}),"\n",(0,o.jsxs)(t.ol,{children:["\n",(0,o.jsx)(t.li,{children:"A really awesome workflow engine like LittleHorse."}),"\n",(0,o.jsxs)(t.li,{children:["A ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts/workflows",children:"Workflow Specification"}),", which defines the series of steps in your application."]}),"\n",(0,o.jsxs)(t.li,{children:[(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts/tasks",children:"Task Workers"}),", which are computer programs that execute work when the LH Server tells it to."]}),"\n"]}),"\n",(0,o.jsx)(t.h3,{id:"workflow-specifications",children:"Workflow Specifications"}),"\n",(0,o.jsxs)(t.p,{children:["A Workflow Specification (or ",(0,o.jsx)(t.code,{children:"WfSpec"})," in LittleHorse) is the configuration, or metadata object, that tells the engine what Tasks to run,\nwhat order to run the tasks, ",(0,o.jsx)(t.strong,{children:"how to handle exceptions or failures,"})," what variables are to be passed from task to task, and what inputs and outputs are required to run the workflow."]}),"\n",(0,o.jsxs)(t.p,{children:["In LittleHorse the ",(0,o.jsx)(t.code,{children:"WfSpec"})," is submitted to and held by the LittleHorse server. Users of LittleHorse can define a ",(0,o.jsx)(t.code,{children:"WfSpec"})," in vanilla code (Java/Go/Python) using the LittleHorse SDK. The SDK will compile your vanilla code into a ",(0,o.jsx)(t.code,{children:"WfSpec"})," that the LH Server understands and keeps inside its data store."]}),"\n",(0,o.jsx)(t.admonition,{type:"info",children:(0,o.jsxs)(t.p,{children:["To learn how to write a ",(0,o.jsx)(t.code,{children:"WfSpec"})," in LittleHorse, check out our ",(0,o.jsxs)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/wfspec-development",children:[(0,o.jsx)(t.code,{children:"WfSpec"})," Development docs"]}),"."]})}),"\n",(0,o.jsx)(t.p,{children:"In the background LittleHorse server takes the submitted spec from the SDK, and compiles a protobuf object that is submitted to the LittleHorse server."}),"\n",(0,o.jsx)(t.p,{children:"For example, the following code in Java defines a two-step workflow in which we look up the price of an item, charge a customer's credit card, and then ship an item."}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-java",children:'public class ECommerceWorkflow {\n\n public void checkoutWorkflow(WorkflowThread wf) {\n // Create some Workflow Variables\n var customerId = wf.addVariable("customer-id", VariableType.STR).searchable().required();\n var itemId = wf.addVariable("item-id", VariableType.STR).required();\n var price = wf.addVariable("price", VariableType.INT);\n\n // Fetch Price and save it into a variable\n var priceOutput = wf.execute("calculate-price", itemId);\n wf.mutate(price, VariableMutationType.ASSIGN, priceOutput);\n\n // Charge credit card (passing in the output from previous task)\n wf.execute("charge-credit-card", customerId, price);\n\n // Ship item\n wf.execute("ship-item", customerId, itemId);\n }\n}\n'})}),"\n",(0,o.jsx)(t.admonition,{type:"note",children:(0,o.jsx)(t.p,{children:"Just by using LittleHorse to define the above workflow, you get reliability, observability, retries, and governance out of the box!"})}),"\n",(0,o.jsx)(t.h3,{id:"tasks-and-task-workers",children:"Tasks and Task Workers"}),"\n",(0,o.jsx)(t.p,{children:"Tasks are the unit of work that can be executed a workflow engine. It's best to think in examples:"}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsx)(t.li,{children:"Change lower case letters to upper case letters."}),"\n",(0,o.jsx)(t.li,{children:"Call an API with an input variable and pass along the output."}),"\n",(0,o.jsx)(t.li,{children:"Fetch data from a database."}),"\n",(0,o.jsx)(t.li,{children:"Convert a message from HL7 version 2.5 to HL7 version 3."}),"\n"]}),"\n",(0,o.jsx)(t.p,{children:"Task workers are programs that use the LittleHorse SDK, connect to LittleHorse, and execute tasks when the workflow says it's time to do so."}),"\n",(0,o.jsx)(t.admonition,{type:"tip",children:(0,o.jsxs)(t.p,{children:["To learn how to write a Task Worker, check out our ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/task-worker-development",children:"Task Worker Development Guide"}),"."]})}),"\n",(0,o.jsxs)(t.p,{children:["You can also use ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts/external-events",children:"External Events"})," or ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"})," to wait for input from a human user or an external system (like a callback or webhook)."]}),"\n",(0,o.jsx)(t.h3,{id:"workflow-clients",children:"Workflow Clients"}),"\n",(0,o.jsxs)(t.p,{children:["Lastly you need to tell LittleHorse when to run a workflow. You can do it with our CLI (",(0,o.jsx)(t.code,{children:"lhctl"}),") but in production you'll need to use the LittleHorse SDK to kick off a workflow. You can do this with our page on ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/running-workflows",children:"Running Workflows using grpc"})]}),"\n",(0,o.jsxs)(t.p,{children:["You'll also need to tell LittleHorse about External Events that happen. You can also do this using ",(0,o.jsx)(t.code,{children:"lhctl"})," or ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/posting-external-events",children:"with our SDK's"}),"."]}),"\n",(0,o.jsx)(t.h2,{id:"littlehorse-use-cases",children:"LittleHorse Use-Cases"}),"\n",(0,o.jsx)(t.p,{children:"There are many different types of workflow engines, each of which supports different use-cases. For example:"}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:[(0,o.jsx)(t.strong,{children:"Batch ETL and Cronjob"})," workflows are automated by systems like Apache Airflow and Dagster."]}),"\n",(0,o.jsxs)(t.li,{children:[(0,o.jsx)(t.strong,{children:"Infrastructure Provisioning and Configuration"})," workflows can be automated by Ansible, Argo, and Jenkins."]}),"\n",(0,o.jsxs)(t.li,{children:[(0,o.jsx)(t.strong,{children:"IT Integration and BPM"})," workflows may be automated by systems like Camunda and jBPM."]}),"\n"]}),"\n",(0,o.jsxs)(t.p,{children:["However, ",(0,o.jsx)(t.strong,{children:"LittleHorse allows you to orchestrate business processes across your software systems."})," Some use-cases are included below."]}),"\n",(0,o.jsx)(t.h3,{id:"microservices",children:"Microservices"}),"\n",(0,o.jsxs)(t.p,{children:["All microservice-based applications are inherently distributed systems with the goal of supporting some business process (because no one writes microservices for the sake of writing code, right?). While often necessary, microservices ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/blog/challenge-of-microservices",children:"present many challenges"})," to developers due to their distributed nature."]}),"\n",(0,o.jsxs)(t.p,{children:["Our founder Colt McNealy wrote a ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/blog/microservices-and-workflow",children:"detailed blog"})," about how a workflow engine's reliabile state management and oversight can mitigate some of the problems inherent in microservices. Check it out!"]}),"\n",(0,o.jsx)(t.h3,{id:"human-in-the-loop",children:"Human-in-the-Loop"}),"\n",(0,o.jsx)(t.p,{children:"Workflows often need to get input from humans:"}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsx)(t.li,{children:"Approval flows."}),"\n",(0,o.jsx)(t.li,{children:"Waiting for information from customers."}),"\n",(0,o.jsx)(t.li,{children:"Handling exceptional scenarios."}),"\n"]}),"\n",(0,o.jsxs)(t.p,{children:["That's hard to coordinate without a workflow engine. You'd have to build your own state management system that correlates tasks to workflows. LittleHorse ",(0,o.jsx)(t.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"})," make this much easier."]}),"\n",(0,o.jsx)(t.h3,{id:"rag-and-ai",children:"RAG and AI"}),"\n",(0,o.jsx)(t.p,{children:"AI is only useful when you call it at the right time, with the right inputs, and do something with the outputs. That's a workflow. And all sorts of things can go wrong when using LLM's, which is why you need to have a robust workflow engine to provide oversight and exception handling."}),"\n",(0,o.jsx)(t.h3,{id:"legacy-system-modernization",children:"Legacy System Modernization"}),"\n",(0,o.jsx)(t.p,{children:"Whether you are integrating legacy systems that you inherited from the past, or integrating multiple tech stacks accrued through M&A, your customers expect a real-time experience that seamlessly spans all of your systems. Workflow engines are useful for reliably orchestrating actions and moving data across multiple different systems."}),"\n",(0,o.jsx)(t.h3,{id:"api-gateway",children:"API gateway"}),"\n",(0,o.jsx)(t.p,{children:"If we look at the properties of an API gateway and how they are used, a workflow engine makes sense."}),"\n",(0,o.jsxs)(t.p,{children:["The usage of an API gateway is to have a single layer that abstracts further endpoints.",(0,o.jsx)(t.br,{}),"\n","In practice this most often means calling the same API gateway multiple times, receiving the requested data, and doing some date manipulation or calculations at the application layer.\nA workflow engine performs all of the most common actions, and includes things like centralized security, possible data obscurity, failure handling, observability and allows for operators to scale compute.\nAll while still maintaining a central plane that can be shared across an entire orginization.",(0,o.jsx)(t.br,{}),"\n","Additionally a workflow engine still allows for the standard CRUD(Create, Read, Update, Delete) operations that an API gateway provides."]})]})}function d(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>r,x:()=>a});var o=s(6540);const n={},i=o.createContext(n);function r(e){const t=o.useContext(i);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:r(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/32279f3c.1fb5b3e6.js b/assets/js/32279f3c.1fb5b3e6.js deleted file mode 100644 index 208cf8dd4..000000000 --- a/assets/js/32279f3c.1fb5b3e6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1634],{3715:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>c,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var a=n(4848),o=n(8453);const s={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},r="Integration Patterns: SAGA Transactions",i={permalink:"/blog/saga-pattern",source:"@site/blog/2024-09-24-saga-pattern.md",title:"Integration Patterns: SAGA Transactions",description:"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator.",date:"2024-09-24T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:5.72,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,nextItem:{title:"Microservices and Workflow: A Match Made in Heaven",permalink:"/blog/microservices-and-workflow"}},c={authorsImageUrls:[void 0]},l=[];function u(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,a.jsx)(e.p,{children:"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator."})}function h(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,a.jsx)(e,{...t,children:(0,a.jsx)(u,{...t})}):u(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>i});var a=n(6540);const o={},s=a.createContext(o);function r(t){const e=a.useContext(s);return a.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function i(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),a.createElement(s.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/32279f3c.96e1a56d.js b/assets/js/32279f3c.96e1a56d.js new file mode 100644 index 000000000..ea8e833aa --- /dev/null +++ b/assets/js/32279f3c.96e1a56d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1634],{3715:(t,e,a)=>{a.r(e),a.d(e,{assets:()=>c,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var n=a(4848),o=a(8453);const s={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},r="Integration Patterns: Saga Transactions",i={permalink:"/blog/saga-pattern",source:"@site/blog/2024-09-24-saga-pattern.md",title:"Integration Patterns: Saga Transactions",description:"The Saga pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the Saga pattern is tricky to get right without an orchestrator.",date:"2024-09-24T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:5.855,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,nextItem:{title:"The Basics of Workflow",permalink:"/blog/basics-of-workflow"}},c={authorsImageUrls:[void 0]},l=[];function u(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,n.jsx)(e.p,{children:"The Saga pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the Saga pattern is tricky to get right without an orchestrator."})}function h(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,n.jsx)(e,{...t,children:(0,n.jsx)(u,{...t})}):u(t)}},8453:(t,e,a)=>{a.d(e,{R:()=>r,x:()=>i});var n=a(6540);const o={},s=n.createContext(o);function r(t){const e=n.useContext(s);return n.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function i(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),n.createElement(s.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/366d7c2c.0f6833c6.js b/assets/js/366d7c2c.0f6833c6.js new file mode 100644 index 000000000..cca187684 --- /dev/null +++ b/assets/js/366d7c2c.0f6833c6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[4325],{6552:e=>{e.exports=JSON.parse('{"author":{"name":"Mitchell Henderson","title":"Principal Technical Architect","description":"Mitch is a software industry veteran experienced with Apache Kafka, Apache Cassandra, and systems modernization across a diverse set of industries including financial services, healthcare, technology, retail, and manufacturing. He prides himself in ensuring that all users of LittleHorse are successful.","page":{"permalink":"/blog/authors/mitchellh"},"socials":{"github":"https://github.com/mitchell-h","linkedin":"https://www.linkedin.com/in/mitchellghenderson/"},"imageURL":"https://avatars.githubusercontent.com/u/6223426","key":"mitchellh","count":1},"listMetadata":{"permalink":"/blog/authors/mitchellh","page":1,"postsPerPage":20,"totalPages":1,"totalCount":1,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file diff --git a/assets/js/3a2db09e.6a734090.js b/assets/js/3a2db09e.c5741c99.js similarity index 77% rename from assets/js/3a2db09e.6a734090.js rename to assets/js/3a2db09e.c5741c99.js index 7a95725a7..691d408aa 100644 --- a/assets/js/3a2db09e.6a734090.js +++ b/assets/js/3a2db09e.c5741c99.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8121],{8070:e=>{e.exports=JSON.parse('{"tags":[{"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture.","count":4},{"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator.","count":7},{"label":"LittleHorse Releases","permalink":"/blog/tags/release/","description":"Release blogs for LittleHorse Orchestrator.","count":7}]}')}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8121],{8070:e=>{e.exports=JSON.parse('{"tags":[{"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture.","count":5},{"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator.","count":8},{"label":"LittleHorse Releases","permalink":"/blog/tags/release/","description":"Release blogs for LittleHorse Orchestrator.","count":7}]}')}}]); \ No newline at end of file diff --git a/assets/js/5a5f8fd5.184a378a.js b/assets/js/5a5f8fd5.47e8017b.js similarity index 76% rename from assets/js/5a5f8fd5.184a378a.js rename to assets/js/5a5f8fd5.47e8017b.js index 15e819eb5..0274a732e 100644 --- a/assets/js/5a5f8fd5.184a378a.js +++ b/assets/js/5a5f8fd5.47e8017b.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8035],{4884:t=>{t.exports=JSON.parse('{"tag":{"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator.","allTagsPath":"/blog/tags","count":7,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/littlehorse/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":7,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8035],{4884:t=>{t.exports=JSON.parse('{"tag":{"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator.","allTagsPath":"/blog/tags","count":8,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/littlehorse/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":8,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.5119f5bb.js b/assets/js/814f3328.5119f5bb.js deleted file mode 100644 index 2255726b9..000000000 --- a/assets/js/814f3328.5119f5bb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Integration Patterns: SAGA Transactions","permalink":"/blog/saga-pattern","unlisted":false,"date":"2024-09-24T00:00:00.000Z"},{"title":"Microservices and Workflow: A Match Made in Heaven","permalink":"/blog/microservices-and-workflow","unlisted":false,"date":"2024-09-02T00:00:00.000Z"},{"title":"Releasing 0.11","permalink":"/blog/littlehorse-0.11-release","unlisted":false,"date":"2024-08-31T00:00:00.000Z"},{"title":"The Challenge of Microservices","permalink":"/blog/challenge-of-microservices","unlisted":false,"date":"2024-08-27T00:00:00.000Z"},{"title":"The Promise of Microservices","permalink":"/blog/promise-of-microservices","unlisted":false,"date":"2024-08-22T00:00:00.000Z"},{"title":"Releasing 0.10","permalink":"/blog/littlehorse-0.10-release","unlisted":false,"date":"2024-07-12T00:00:00.000Z"},{"title":"Releasing 0.9","permalink":"/blog/littlehorse-0.9-release","unlisted":false,"date":"2024-06-24T00:00:00.000Z"},{"title":"Releasing 0.8","permalink":"/blog/littlehorse-0.8-release","unlisted":false,"date":"2024-03-26T00:00:00.000Z"},{"title":"Releasing 0.7","permalink":"/blog/littlehorse-0.7-release","unlisted":false,"date":"2024-01-28T00:00:00.000Z"},{"title":"Releasing 0.5.0","permalink":"/blog/littlehorse-0.5.0-release","unlisted":false,"date":"2023-09-08T00:00:00.000Z"},{"title":"Releasing 0.2.0","permalink":"/blog/littlehorse-0.2.0-release","unlisted":false,"date":"2023-08-30T00:00:00.000Z"}]}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.9db599c7.js b/assets/js/814f3328.9db599c7.js new file mode 100644 index 000000000..57137dcc4 --- /dev/null +++ b/assets/js/814f3328.9db599c7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Integration Patterns: Saga Transactions","permalink":"/blog/saga-pattern","unlisted":false,"date":"2024-09-24T00:00:00.000Z"},{"title":"The Basics of Workflow","permalink":"/blog/basics-of-workflow","unlisted":false,"date":"2024-09-04T00:00:00.000Z"},{"title":"Microservices and Workflow: A Match Made in Heaven","permalink":"/blog/microservices-and-workflow","unlisted":false,"date":"2024-09-02T00:00:00.000Z"},{"title":"Releasing 0.11","permalink":"/blog/littlehorse-0.11-release","unlisted":false,"date":"2024-08-31T00:00:00.000Z"},{"title":"The Challenge of Microservices","permalink":"/blog/challenge-of-microservices","unlisted":false,"date":"2024-08-27T00:00:00.000Z"},{"title":"The Promise of Microservices","permalink":"/blog/promise-of-microservices","unlisted":false,"date":"2024-08-22T00:00:00.000Z"},{"title":"Releasing 0.10","permalink":"/blog/littlehorse-0.10-release","unlisted":false,"date":"2024-07-12T00:00:00.000Z"},{"title":"Releasing 0.9","permalink":"/blog/littlehorse-0.9-release","unlisted":false,"date":"2024-06-24T00:00:00.000Z"},{"title":"Releasing 0.8","permalink":"/blog/littlehorse-0.8-release","unlisted":false,"date":"2024-03-26T00:00:00.000Z"},{"title":"Releasing 0.7","permalink":"/blog/littlehorse-0.7-release","unlisted":false,"date":"2024-01-28T00:00:00.000Z"},{"title":"Releasing 0.5.0","permalink":"/blog/littlehorse-0.5.0-release","unlisted":false,"date":"2023-09-08T00:00:00.000Z"},{"title":"Releasing 0.2.0","permalink":"/blog/littlehorse-0.2.0-release","unlisted":false,"date":"2023-08-30T00:00:00.000Z"}]}')}}]); \ No newline at end of file diff --git a/assets/js/84954295.588f31c1.js b/assets/js/84954295.588f31c1.js new file mode 100644 index 000000000..7dca690ff --- /dev/null +++ b/assets/js/84954295.588f31c1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7757],{6010:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>a,metadata:()=>r,toc:()=>c});var s=n(4848),i=n(8453);const a={slug:"basics-of-workflow",authors:["mitchellh"],tags:["analysis","littlehorse"]},o="The Basics of Workflow",r={permalink:"/blog/basics-of-workflow",source:"@site/blog/2024-09-04-basics-of-workflows.md",title:"The Basics of Workflow",description:"LittleHorse Enterprises is a workflow engine company. But what is a workflow engine?",date:"2024-09-04T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:5.675,hasTruncateMarker:!0,authors:[{name:"Mitchell Henderson",title:"Principal Technical Architect",description:"Mitch is a software industry veteran experienced with Apache Kafka, Apache Cassandra, and systems modernization across a diverse set of industries including financial services, healthcare, technology, retail, and manufacturing. He prides himself in ensuring that all users of LittleHorse are successful.",page:{permalink:"/blog/authors/mitchellh"},socials:{github:"https://github.com/mitchell-h",linkedin:"https://www.linkedin.com/in/mitchellghenderson/"},imageURL:"https://avatars.githubusercontent.com/u/6223426",key:"mitchellh"}],frontMatter:{slug:"basics-of-workflow",authors:["mitchellh"],tags:["analysis","littlehorse"]},unlisted:!1,prevItem:{title:"Integration Patterns: Saga Transactions",permalink:"/blog/saga-pattern"},nextItem:{title:"Microservices and Workflow: A Match Made in Heaven",permalink:"/blog/microservices-and-workflow"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={p:"p",...(0,i.R)(),...e.components};return(0,s.jsx)(t.p,{children:"LittleHorse Enterprises is a workflow engine company. But what is a workflow engine?"})}function u(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>o,x:()=>r});var s=n(6540);const i={},a=s.createContext(i);function o(e){const t=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a957f15c.b1f52bc4.js b/assets/js/a957f15c.26e73b5e.js similarity index 59% rename from assets/js/a957f15c.b1f52bc4.js rename to assets/js/a957f15c.26e73b5e.js index ed18b242c..bfdbdfabe 100644 --- a/assets/js/a957f15c.b1f52bc4.js +++ b/assets/js/a957f15c.26e73b5e.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[5174],{488:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>i,toc:()=>l});var a=n(4848),o=n(8453);const r={slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},s=void 0,i={permalink:"/blog/microservices-and-workflow",source:"@site/blog/2024-09-02-microservices-and-workflow.md",title:"Microservices and Workflow: A Match Made in Heaven",description:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.",date:"2024-09-02T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.575,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"Integration Patterns: SAGA Transactions",permalink:"/blog/saga-pattern"},nextItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"}},c={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,o.R)(),...e.components};return(0,a.jsx)(t.p,{children:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices."})}function u(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,a.jsx)(t,{...e,children:(0,a.jsx)(h,{...e})}):h(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>i});var a=n(6540);const o={},r=a.createContext(o);function s(e){const t=a.useContext(r);return a.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function i(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),a.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[5174],{488:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>i,toc:()=>l});var o=n(4848),a=n(8453);const r={slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},s=void 0,i={permalink:"/blog/microservices-and-workflow",source:"@site/blog/2024-09-02-microservices-and-workflow.md",title:"Microservices and Workflow: A Match Made in Heaven",description:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.",date:"2024-09-02T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.575,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"The Basics of Workflow",permalink:"/blog/basics-of-workflow"},nextItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"}},c={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,a.R)(),...e.components};return(0,o.jsx)(t.p,{children:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices."})}function u(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>i});var o=n(6540);const a={},r=o.createContext(a);function s(e){const t=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function i(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),o.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b136319f.3606fa01.js b/assets/js/b136319f.3606fa01.js deleted file mode 100644 index 3adc74df2..000000000 --- a/assets/js/b136319f.3606fa01.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7762],{7393:(e,s,n)=>{n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var t=n(4848),r=n(8453);const i={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},a="Integration Patterns: SAGA Transactions",o={permalink:"/blog/saga-pattern",source:"@site/blog/2024-09-24-saga-pattern.md",title:"Integration Patterns: SAGA Transactions",description:"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator.",date:"2024-09-24T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:5.72,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,nextItem:{title:"Microservices and Workflow: A Match Made in Heaven",permalink:"/blog/microservices-and-workflow"}},l={authorsImageUrls:[void 0]},c=[{value:"The SAGA Pattern",id:"the-saga-pattern",level:2},{value:"Use Cases",id:"use-cases",level:3},{value:"Implementation",id:"implementation",level:3},{value:"Case Study: Order Processing",id:"case-study-order-processing",level:2},{value:"Using Message Queues",id:"using-message-queues",level:3},{value:"Using LittleHorse",id:"using-littlehorse",level:3},{value:"Wrapping Up",id:"wrapping-up",level:2},{value:"Get Involved!",id:"get-involved",level:3}];function d(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.p,{children:"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator."}),"\n",(0,t.jsxs)(s.admonition,{type:"info",children:[(0,t.jsx)(s.p,{children:"This is the first part in a five-part blog series on useful Integration Patterns. This blog series will help you build real-time, responsive applications and microservices that produce predictable results and prevent the Grumpy Customer Problem."}),(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"[This Post]"})," SAGA Transactions"]}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] The Outbox Pattern"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Retries and Dead-Letter Queues"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Callbacks and External Events"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Queuing and Backpressure"}),"\n"]})]}),"\n",(0,t.jsx)(s.h2,{id:"the-saga-pattern",children:"The SAGA Pattern"}),"\n",(0,t.jsxs)(s.p,{children:["At a technical level, the ",(0,t.jsx)(s.a,{href:"https://microservices.io/patterns/data/saga.html",children:"SAGA Pattern"})," allows you to perform distributed transactions across multiple disparate systems without 2-phase commit."]}),"\n",(0,t.jsx)(s.p,{children:"In plain English, it is a tool in the belt of a software engineer to prevent half-fulfilled bank transfers, hanging orders, or other failures which would result in a Grumpy Customer."}),"\n",(0,t.jsx)(s.h3,{id:"use-cases",children:"Use Cases"}),"\n",(0,t.jsx)(s.p,{children:"Business processes often need to perform actions in two separate systems either all at once or not at all. For example, you may need to charge a customer's credit card, reserve inventory, and ship an item to the customer all at once or not at all. If the payment went through but shipping failed, we would see the Grumpy Customer Problem yet again."}),"\n",(0,t.jsx)(s.p,{children:"The SAGA pattern is appropriate when:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"A business process must take action across multiple separate systems (legacy monoliths, microservices, external API's, etc),"}),"\n",(0,t.jsx)(s.li,{children:'Each of those actions can be undone via a "compensation task", and'}),"\n",(0,t.jsx)(s.li,{children:"All actions must logically happen together or not at all."}),"\n"]}),"\n",(0,t.jsx)(s.admonition,{type:"tip",children:(0,t.jsxs)(s.p,{children:["It's also worth noting that a different flavor of the SAGA pattern can also be used in ",(0,t.jsx)(s.em,{children:"long-running"})," business processes. In a past job, for example, I worked on a project that implemented the SAGA pattern to handle the scheduling of home inspections. In this case, the task of finding an inspector to show up at the home and confirming a time with the homeowner needed to be performed atomically."]})}),"\n",(0,t.jsx)(s.h3,{id:"implementation",children:"Implementation"}),"\n",(0,t.jsx)(s.p,{children:"While SAGA is very hard to implement, it's simple to describe:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"Try to perform the actions across the multiple systems."}),"\n",(0,t.jsxs)(s.li,{children:["If one of the actions fails, then run a ",(0,t.jsx)(s.em,{children:"compensation"})," for all previously-executed tasks."]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.em,{children:"compensation"}),' is simply an action that "undoes" the previous action. For example, the compensation for a payment task might be to issue a refund.']}),"\n",(0,t.jsx)(s.h2,{id:"case-study-order-processing",children:"Case Study: Order Processing"}),"\n",(0,t.jsxs)(s.p,{children:["Let's take a look at a familiar use-case: an order processing workflow involving the the ",(0,t.jsx)(s.code,{children:"inventory"})," service, and the ",(0,t.jsx)(s.code,{children:"payments"})," service. (The ",(0,t.jsx)(s.code,{children:"orders"})," service is involved implicitly.) As they would in a real world scenario, all of our services live on separate physical systems and have their own databases."]}),"\n",(0,t.jsx)(s.p,{children:"In this business process, we first reserve inventory for the ordered item. Next, we charge the customer's credit card."}),"\n",(0,t.jsx)(s.p,{children:"If charging the credit card fails, then we have a problem: we've reserved inventory but not sold it."}),"\n",(0,t.jsxs)(s.p,{children:["Our services need the following functionality. In SOA, these would be endpoints; in LittleHorse, they would be ",(0,t.jsx)(s.code,{children:"TaskDef"}),"s:"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"create-order"}),": creates an order in the ",(0,t.jsx)(s.code,{children:"PENDING"})," status."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"reserve-inventory"}),": marks an item as no longer available for sale."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"charge-payment"}),": charges the customer."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"release-inventory"}),": marks an item as available for sale again."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"cancel-order"}),": marks an order as ",(0,t.jsx)(s.code,{children:"CANCELED"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"complete-order"}),": marks an order as ",(0,t.jsx)(s.code,{children:"COMPLETED"}),"."]}),"\n"]}),"\n",(0,t.jsx)(s.h3,{id:"using-message-queues",children:"Using Message Queues"}),"\n",(0,t.jsx)(s.p,{children:"Using message queues, the happy path looks like the following:"}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Architecture diagram",src:n(4719).A+"",width:"544",height:"493"})}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["The above image assumes the ",(0,t.jsx)(s.em,{children:"choreography"})," pattern, in contrast to the ",(0,t.jsx)(s.em,{children:"orchestrator"})," pattern. The orchestrator pattern is a ton of work and involves writing something that very much resembles LittleHorse!"]})}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Orders service calls ",(0,t.jsx)(s.code,{children:"createOrder()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service publishes to the ",(0,t.jsx)(s.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the message and calls ",(0,t.jsx)(s.code,{children:"reserveInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"charge-payment"})," queue."]}),"\n",(0,t.jsx)(s.li,{children:"Payment service charges the credit card."}),"\n",(0,t.jsxs)(s.li,{children:["Payment service publishes to the ",(0,t.jsx)(s.code,{children:"complete-order"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service consumes the record and calls ",(0,t.jsx)(s.code,{children:"completeOrder()"}),"."]}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"In just the happy path, we have strong coupling already between our services in three places, and we have three message queues to manage."}),"\n",(0,t.jsx)(s.p,{children:"But now we need to release the inventory and cancel the order when the payment doesn't go through. So the flow looks like this:"}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Architecture Diagram",src:n(8525).A+"",width:"712",height:"493"})}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Orders service calls ",(0,t.jsx)(s.code,{children:"createOrder()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service publishes to the ",(0,t.jsx)(s.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the message and calls ",(0,t.jsx)(s.code,{children:"reserveInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"charge-payment"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Payment service charges the credit card ",(0,t.jsx)(s.em,{children:"unsuccessfully"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Payment service publishes to the ",(0,t.jsx)(s.code,{children:"release-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the record and calls ",(0,t.jsx)(s.code,{children:"releaseInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"cancel-order"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service consumes the record and calls ",(0,t.jsx)(s.code,{children:"cancelOrder()"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["Now, we have ",(0,t.jsx)(s.em,{children:"five"})," different message queues that we have to wrangle with. We can also see that the overall business flow has started to leak across all of our different services."]}),"\n",(0,t.jsx)(s.admonition,{type:"danger",children:(0,t.jsxs)(s.p,{children:["One thing we are ignoring in this blog post is ",(0,t.jsx)(s.em,{children:"reliability"}),": to make this setup production-ready, we would also have to ensure that updates to the internal databases of the services are atomic along with pushing messages to the message queue. We will cover that in next week's post (along with how LittleHorse takes care of that for you)."]})}),"\n",(0,t.jsx)(s.h3,{id:"using-littlehorse",children:"Using LittleHorse"}),"\n",(0,t.jsxs)(s.p,{children:["Using LittleHorse, in java, this whole workflow could look like the following. This is ",(0,t.jsx)(s.em,{children:"real code"})," that does indeed compile and replaces the need for all of the complex queueing logic we had above."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-java",children:'public void sagaExample(WorkflowThread wf) {\n var item = wf.addVariable("item", STR);\n var customer = wf.addVariable("customer", STR);\n var price = wf.addVariable("price", DOUBLE);\n var orderId = wf.addVariable("order-id", STR);\n\n wf.execute("create-order", orderId);\n wf.execute("reserve-inventory", item, orderId);\n\n NodeOutput paymentResult = wf.execute("charge-payment", customer, price);\n // SAGA here!!\n wf.handleException("credit-card-declined", handler -> {\n handler.execute("release-inventory", item, orderId);\n handler.execute("cancel-order", orderId);\n handler.fail("credit-card-declined", "Credit card was declined. Order canceled!");\n });\n\n wf.execute("complete-order", orderId);\n}\n'})}),"\n",(0,t.jsxs)(s.p,{children:["Instead of managing five message queues and five strongly-coupled integration points between microservices, all we need to do is register the workflow, define ",(0,t.jsx)(s.em,{children:"truly"})," modular tasks, and let LittleHorse take care of the rest."]}),"\n",(0,t.jsx)(s.h2,{id:"wrapping-up",children:"Wrapping Up"}),"\n",(0,t.jsxs)(s.p,{children:["The SAGA Pattern is one of five tools we will cover in this series on avoiding the Grumpy Customer Problem. It's simple to understand but ",(0,t.jsx)(s.em,{children:"painfully complex"})," to implement. Fortunately, LittleHorse makes it easier!"]}),"\n",(0,t.jsxs)(s.admonition,{type:"note",children:[(0,t.jsxs)(s.p,{children:["A careful reader, or anyone who ",(0,t.jsx)(s.a,{href:"https://www.linkedin.com/feed/update/urn:li:activity:7244572885179121664/",children:"reads my rants on LinkedIn"}),", might note that in order to make the order processing workflow truly reliable, we would also need to do something like the Outbox pattern or Event Sourcing."]}),(0,t.jsx)(s.p,{children:"That is true, and we'll cover it in the next post (and you'll see how LittleHorse does that for you automatically!)."})]}),"\n",(0,t.jsx)(s.h3,{id:"get-involved",children:"Get Involved!"}),"\n",(0,t.jsx)(s.p,{children:"Stay tuned for the next post on the Transactional Outbox Pattern! In the meantime:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Try out our ",(0,t.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"Quickstarts"})]}),"\n",(0,t.jsxs)(s.li,{children:["Join us ",(0,t.jsx)(s.a,{href:"https://launchpass.com/littlehorsecommunity",children:"on Slack"})]}),"\n",(0,t.jsxs)(s.li,{children:["Give us a star ",(0,t.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"on GitHub"}),"!"]}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8525:(e,s,n)=>{n.d(s,{A:()=>t});const t=n.p+"assets/images/2024-09-24-choreography-saga-b400a44518ce7213d491df50bad0bb72.png"},4719:(e,s,n)=>{n.d(s,{A:()=>t});const t=n.p+"assets/images/2024-09-24-choreography-simple-830e1fcf682eb3cde8b40210f92b3dd2.png"},8453:(e,s,n)=>{n.d(s,{R:()=>a,x:()=>o});var t=n(6540);const r={},i=t.createContext(r);function a(e){const s=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function o(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),t.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b136319f.72a954d8.js b/assets/js/b136319f.72a954d8.js new file mode 100644 index 000000000..574623c46 --- /dev/null +++ b/assets/js/b136319f.72a954d8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7762],{7393:(e,s,n)=>{n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var t=n(4848),r=n(8453);const i={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},a="Integration Patterns: Saga Transactions",o={permalink:"/blog/saga-pattern",source:"@site/blog/2024-09-24-saga-pattern.md",title:"Integration Patterns: Saga Transactions",description:"The Saga pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the Saga pattern is tricky to get right without an orchestrator.",date:"2024-09-24T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:5.855,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,nextItem:{title:"The Basics of Workflow",permalink:"/blog/basics-of-workflow"}},l={authorsImageUrls:[void 0]},c=[{value:"The Saga Pattern",id:"the-saga-pattern",level:2},{value:"Use Cases",id:"use-cases",level:3},{value:"Implementation",id:"implementation",level:3},{value:"Case Study: Order Processing",id:"case-study-order-processing",level:2},{value:"Using Message Queues",id:"using-message-queues",level:3},{value:"Using LittleHorse",id:"using-littlehorse",level:3},{value:"Wrapping Up",id:"wrapping-up",level:2},{value:"Get Involved!",id:"get-involved",level:3}];function d(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.p,{children:"The Saga pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the Saga pattern is tricky to get right without an orchestrator."}),"\n",(0,t.jsxs)(s.admonition,{type:"info",children:[(0,t.jsx)(s.p,{children:"This is the first part in a five-part blog series on useful Integration Patterns. This blog series will help you build real-time, responsive applications and microservices that produce predictable results and prevent the Grumpy Customer Problem."}),(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"[This Post]"})," Saga Transactions"]}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] The Outbox Pattern"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Retries and Dead-Letter Queues"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Callbacks and External Events"}),"\n",(0,t.jsx)(s.li,{children:"[Coming soon] Queuing and Backpressure"}),"\n"]})]}),"\n",(0,t.jsx)(s.h2,{id:"the-saga-pattern",children:"The Saga Pattern"}),"\n",(0,t.jsxs)(s.p,{children:["At a technical level, the ",(0,t.jsx)(s.a,{href:"https://microservices.io/patterns/data/saga.html",children:"Saga Pattern"})," allows you to perform distributed transactions across multiple disparate systems without 2-phase commit."]}),"\n",(0,t.jsx)(s.p,{children:"In plain English, it is a tool in the belt of a software engineer to prevent half-fulfilled bank transfers, hanging orders, or other failures which would result in a Grumpy Customer."}),"\n",(0,t.jsx)(s.admonition,{type:"info",children:(0,t.jsx)(s.p,{children:'The "Saga" pattern gets its name from literature and film, wherein a "saga" is a series of chronologically-ordered related works. For example, the "Star Wars Saga."'})}),"\n",(0,t.jsx)(s.h3,{id:"use-cases",children:"Use Cases"}),"\n",(0,t.jsx)(s.p,{children:"Business processes often need to perform actions in two separate systems either all at once or not at all. For example, you may need to charge a customer's credit card, reserve inventory, and ship an item to the customer all at once or not at all. If the payment went through but shipping failed, we would see the Grumpy Customer Problem yet again."}),"\n",(0,t.jsx)(s.p,{children:"The Saga pattern is appropriate when:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"A business process must take action across multiple separate systems (legacy monoliths, microservices, external API's, etc),"}),"\n",(0,t.jsx)(s.li,{children:'Each of those actions can be undone via a "compensation task", and'}),"\n",(0,t.jsx)(s.li,{children:"All actions must logically happen together or not at all."}),"\n"]}),"\n",(0,t.jsx)(s.admonition,{type:"tip",children:(0,t.jsxs)(s.p,{children:["It's also worth noting that a different flavor of the Saga pattern can also be used in ",(0,t.jsx)(s.em,{children:"long-running"})," business processes. In a past job, for example, I worked on a project that implemented the Saga pattern to handle the scheduling of home inspections. In this case, the task of finding an inspector to show up at the home and confirming a time with the homeowner needed to be performed atomically."]})}),"\n",(0,t.jsx)(s.h3,{id:"implementation",children:"Implementation"}),"\n",(0,t.jsx)(s.p,{children:"While Saga is very hard to implement, it's simple to describe:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:"Try to perform the actions across the multiple systems."}),"\n",(0,t.jsxs)(s.li,{children:["If one of the actions fails, then run a ",(0,t.jsx)(s.em,{children:"compensation"})," for all previously-executed tasks."]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.em,{children:"compensation"}),' is simply an action that "undoes" the previous action. For example, the compensation for a payment task might be to issue a refund.']}),"\n",(0,t.jsx)(s.h2,{id:"case-study-order-processing",children:"Case Study: Order Processing"}),"\n",(0,t.jsxs)(s.p,{children:["Let's take a look at a familiar use-case: an order processing workflow involving the ",(0,t.jsx)(s.code,{children:"inventory"})," service, and the ",(0,t.jsx)(s.code,{children:"payments"})," service. (The ",(0,t.jsx)(s.code,{children:"orders"})," service is involved implicitly.) As they would in a real world scenario, all of our services live on separate physical systems and have their own databases."]}),"\n",(0,t.jsx)(s.p,{children:"In this business process, we first reserve inventory for the ordered item. Next, we charge the customer's credit card."}),"\n",(0,t.jsx)(s.p,{children:"If charging the credit card fails, then we have a problem: we've reserved inventory but not sold it."}),"\n",(0,t.jsxs)(s.p,{children:["Our services need the following functionality. In SOA, these would be endpoints; in LittleHorse, they would be ",(0,t.jsx)(s.code,{children:"TaskDef"}),"s:"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"create-order"}),": creates an order in the ",(0,t.jsx)(s.code,{children:"PENDING"})," status."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"reserve-inventory"}),": marks an item as no longer available for sale."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"charge-payment"}),": charges the customer."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"release-inventory"}),": marks an item as available for sale again."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"cancel-order"}),": marks an order as ",(0,t.jsx)(s.code,{children:"CANCELED"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"complete-order"}),": marks an order as ",(0,t.jsx)(s.code,{children:"COMPLETED"}),"."]}),"\n"]}),"\n",(0,t.jsx)(s.h3,{id:"using-message-queues",children:"Using Message Queues"}),"\n",(0,t.jsx)(s.p,{children:"Using message queues, the happy path looks like the following:"}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Architecture diagram",src:n(4719).A+"",width:"544",height:"493"})}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["The above image assumes the ",(0,t.jsx)(s.em,{children:"choreography"})," pattern, in contrast to the ",(0,t.jsx)(s.em,{children:"orchestrator"})," pattern. The orchestrator pattern is a ton of work and involves writing something that very much resembles LittleHorse!"]})}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Orders service calls ",(0,t.jsx)(s.code,{children:"createOrder()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service publishes to the ",(0,t.jsx)(s.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the message and calls ",(0,t.jsx)(s.code,{children:"reserveInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"charge-payment"})," queue."]}),"\n",(0,t.jsx)(s.li,{children:"Payment service charges the credit card."}),"\n",(0,t.jsxs)(s.li,{children:["Payment service publishes to the ",(0,t.jsx)(s.code,{children:"complete-order"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service consumes the record and calls ",(0,t.jsx)(s.code,{children:"completeOrder()"}),"."]}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"In just the happy path, we have strong coupling already between our services in three places, and we have three message queues to manage."}),"\n",(0,t.jsx)(s.p,{children:"But now we need to release the inventory and cancel the order when the payment doesn't go through. So the flow looks like this:"}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Architecture Diagram",src:n(8525).A+"",width:"712",height:"493"})}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsxs)(s.li,{children:["Orders service calls ",(0,t.jsx)(s.code,{children:"createOrder()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service publishes to the ",(0,t.jsx)(s.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the message and calls ",(0,t.jsx)(s.code,{children:"reserveInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"charge-payment"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Payment service charges the credit card ",(0,t.jsx)(s.em,{children:"unsuccessfully"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Payment service publishes to the ",(0,t.jsx)(s.code,{children:"release-inventory"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service reads the record and calls ",(0,t.jsx)(s.code,{children:"releaseInventory()"}),"."]}),"\n",(0,t.jsxs)(s.li,{children:["Inventory service publishes to the ",(0,t.jsx)(s.code,{children:"cancel-order"})," queue."]}),"\n",(0,t.jsxs)(s.li,{children:["Orders service consumes the record and calls ",(0,t.jsx)(s.code,{children:"cancelOrder()"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["Now, we have ",(0,t.jsx)(s.em,{children:"five"})," different message queues that we have to wrangle with. We can also see that the overall business flow has started to leak across all of our different services."]}),"\n",(0,t.jsx)(s.admonition,{type:"danger",children:(0,t.jsxs)(s.p,{children:["One thing we are ignoring in this blog post is ",(0,t.jsx)(s.em,{children:"reliability"}),": to make this setup production-ready, we would also have to ensure that updates to the internal databases of the services are atomic along with pushing messages to the message queue. We will cover that in next week's post (along with how LittleHorse takes care of that for you)."]})}),"\n",(0,t.jsx)(s.h3,{id:"using-littlehorse",children:"Using LittleHorse"}),"\n",(0,t.jsxs)(s.p,{children:["Using LittleHorse, in java, this whole workflow could look like the following. This is ",(0,t.jsx)(s.em,{children:"real code"})," that does indeed compile and replaces the need for all of the complex queueing logic we had above."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-java",children:'public void sagaExample(WorkflowThread wf) {\n var item = wf.addVariable("item", STR);\n var customer = wf.addVariable("customer", STR);\n var price = wf.addVariable("price", DOUBLE);\n var orderId = wf.addVariable("order-id", STR);\n\n wf.execute("create-order", orderId);\n wf.execute("reserve-inventory", item, orderId);\n\n NodeOutput paymentResult = wf.execute("charge-payment", customer, price);\n // Saga here!!\n wf.handleException("credit-card-declined", handler -> {\n handler.execute("release-inventory", item, orderId);\n handler.execute("cancel-order", orderId);\n handler.fail("credit-card-declined", "Credit card was declined. Order canceled!");\n });\n\n wf.execute("complete-order", orderId);\n}\n'})}),"\n",(0,t.jsxs)(s.p,{children:["Instead of managing five message queues and five strongly-coupled integration points between microservices, all we need to do is register the workflow, define ",(0,t.jsx)(s.em,{children:"truly"})," modular tasks, and let LittleHorse take care of the rest."]}),"\n",(0,t.jsx)(s.h2,{id:"wrapping-up",children:"Wrapping Up"}),"\n",(0,t.jsxs)(s.p,{children:["The Saga Pattern is one of five tools we will cover in this series on avoiding the Grumpy Customer Problem. It's simple to understand but ",(0,t.jsx)(s.em,{children:"painfully complex"})," to implement. Fortunately, LittleHorse makes it easier!"]}),"\n",(0,t.jsxs)(s.admonition,{type:"note",children:[(0,t.jsxs)(s.p,{children:["A careful reader, or anyone who ",(0,t.jsx)(s.a,{href:"https://www.linkedin.com/feed/update/urn:li:activity:7244572885179121664/",children:"reads my rants on LinkedIn"}),", might note that in order to make the order processing workflow truly reliable, we would also need to do something like the Outbox pattern or Event Sourcing."]}),(0,t.jsx)(s.p,{children:"That is true, and we'll cover it in the next post (and you'll see how LittleHorse does that for you automatically!)."})]}),"\n",(0,t.jsx)(s.h3,{id:"get-involved",children:"Get Involved!"}),"\n",(0,t.jsx)(s.p,{children:"Stay tuned for the next post on the Transactional Outbox Pattern! In the meantime:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["Try out our ",(0,t.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"Quickstarts"})]}),"\n",(0,t.jsxs)(s.li,{children:["Join us ",(0,t.jsx)(s.a,{href:"https://launchpass.com/littlehorsecommunity",children:"on Slack"})]}),"\n",(0,t.jsxs)(s.li,{children:["Give us a star ",(0,t.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"on GitHub"}),"!"]}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8525:(e,s,n)=>{n.d(s,{A:()=>t});const t=n.p+"assets/images/2024-09-24-choreography-saga-b400a44518ce7213d491df50bad0bb72.png"},4719:(e,s,n)=>{n.d(s,{A:()=>t});const t=n.p+"assets/images/2024-09-24-choreography-simple-830e1fcf682eb3cde8b40210f92b3dd2.png"},8453:(e,s,n)=>{n.d(s,{R:()=>a,x:()=>o});var t=n(6540);const r={},i=t.createContext(r);function a(e){const s=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function o(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),t.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c15d9823.bee40241.js b/assets/js/c15d9823.1cab491e.js similarity index 80% rename from assets/js/c15d9823.bee40241.js rename to assets/js/c15d9823.1cab491e.js index 33154d101..f1dbada53 100644 --- a/assets/js/c15d9823.bee40241.js +++ b/assets/js/c15d9823.1cab491e.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8146],{9328:e=>{e.exports=JSON.parse('{"metadata":{"permalink":"/blog","page":1,"postsPerPage":20,"totalPages":1,"totalCount":11,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8146],{9328:e=>{e.exports=JSON.parse('{"metadata":{"permalink":"/blog","page":1,"postsPerPage":20,"totalPages":1,"totalCount":12,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file diff --git a/assets/js/df0890b4.6b36611c.js b/assets/js/df0890b4.4efc3585.js similarity index 77% rename from assets/js/df0890b4.6b36611c.js rename to assets/js/df0890b4.4efc3585.js index 1f19bb40b..c609b21d0 100644 --- a/assets/js/df0890b4.6b36611c.js +++ b/assets/js/df0890b4.4efc3585.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7080],{3362:e=>{e.exports=JSON.parse('{"tag":{"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture.","allTagsPath":"/blog/tags","count":4,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/analysis/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":4,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7080],{3362:e=>{e.exports=JSON.parse('{"tag":{"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture.","allTagsPath":"/blog/tags","count":5,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/analysis/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":5,"blogDescription":"The latest news and analysis from your favorite workflow engine.","blogTitle":"LittleHorse OSS Blog"}}')}}]); \ No newline at end of file diff --git a/assets/js/e3618154.02510a92.js b/assets/js/e3618154.02510a92.js deleted file mode 100644 index 4c7d81834..000000000 --- a/assets/js/e3618154.02510a92.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[826],{9100:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>t,metadata:()=>a,toc:()=>c});var n=i(4848),o=i(8453);const t={slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},r=void 0,a={permalink:"/blog/microservices-and-workflow",source:"@site/blog/2024-09-02-microservices-and-workflow.md",title:"Microservices and Workflow: A Match Made in Heaven",description:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.",date:"2024-09-02T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.575,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"Integration Patterns: SAGA Transactions",permalink:"/blog/saga-pattern"},nextItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"}},l={authorsImageUrls:[void 0]},c=[{value:"What is a Workflow?",id:"what-is-a-workflow",level:2},{value:"Workflow Engines",id:"workflow-engines",level:3},{value:"Why Workflow?",id:"why-workflow",level:2},{value:"Mission Critical Oversight",id:"mission-critical-oversight",level:3},{value:"Simple Asynchronous Processing",id:"simple-asynchronous-processing",level:3},{value:"Exception Handling",id:"exception-handling",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.p,{children:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices."}),"\n",(0,n.jsxs)(s.admonition,{type:"info",children:[(0,n.jsx)(s.p,{children:"This is the third and final part of a 3-part blog series:"}),(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"The Promise of Microservices"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices",children:"The Challenge with Microservices"})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"[This Post]"})," Workflow and Microservices: A Match Made in Heaven"]}),"\n"]})]}),"\n",(0,n.jsxs)(s.p,{children:["If you're just joining for the third blog post, we have so far established that microservices are an effective tool for allowing your engineering team to grow beyond just a handful of people working on an enterprise application. However, microservice systems are by nature ",(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservices-are-leaderless",children:(0,n.jsx)(s.strong,{children:"Leaderless"})})," and ",(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservices-are-distributed",children:(0,n.jsx)(s.strong,{children:"Distributed"})}),", which yields challenges in:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#observability",children:(0,n.jsx)(s.strong,{children:"Observability"})}),","]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#reliability-and-correctness",children:(0,n.jsx)(s.strong,{children:"Reliability"})}),", and"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservice-coupling",children:(0,n.jsx)(s.strong,{children:"Complexity Management"})}),"."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Those challenges inspired me to create ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts",children:"LittleHorse"})," in the fall of 2021. LittleHorse provides primitives and guardrails out of the box which make it easier to wrangle with distributed systems and coordinate processes/transactions across multiple microservices."]}),"\n",(0,n.jsx)(s.p,{children:"In this post, we'll discuss:"}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:["What ",(0,n.jsx)(s.em,{children:"workflow"})," means."]}),"\n",(0,n.jsx)(s.li,{children:"How LittleHorse's workflow orchestration capabilities make it easier for you to reliably orchestrate complex business processes."}),"\n"]}),"\n",(0,n.jsxs)(s.admonition,{type:"tip",children:[(0,n.jsx)(s.p,{children:"Want to give LittleHorse a try? Get in touch with us!"}),(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Join the ",(0,n.jsx)(s.a,{href:"https://launchpass.com/littlehorsecommunity",children:(0,n.jsx)(s.strong,{children:"LH Slack Community"})})," for the latest news and help from community experts."]}),"\n",(0,n.jsxs)(s.li,{children:["Check out our ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:(0,n.jsx)(s.strong,{children:"Getting Started"})})," page."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform",children:(0,n.jsx)(s.strong,{children:"Say hello"})})," if you'd like to get in touch with someone from the LittleHorse Enterprises team."]}),"\n"]})]}),"\n",(0,n.jsx)(s.h2,{id:"what-is-a-workflow",children:"What is a Workflow?"}),"\n",(0,n.jsx)(s.p,{children:"A workflow is a blueprint that defines a series of tasks to be performed (perhaps conditioned on certain inputs or external events) in order to achieve a business outcome."}),"\n",(0,n.jsxs)(s.p,{children:["If you recall the e-commerce example from the ",(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#the-nature-of-microservices",children:"previous blog post"}),", you can think of the abstract checkout process as a workflow. This example is interesting because it demonstrates multiple characteristics of common business processes that make microservice development hard."]}),"\n",(0,n.jsx)(s.p,{children:(0,n.jsx)(s.img,{alt:"E-Commerce Checkout Process Diagram",src:i(6216).A+"",width:"1472",height:"406"})}),"\n",(0,n.jsxs)(s.p,{children:["First, a workflow can be ",(0,n.jsx)(s.em,{children:"mission critical"}),". A customer would be very unhappy if the vendor charged their credit card but failed to ship their order. In technical terms, this means that the state of a workflow needs to be consistent and durable, which is hard to achieve in a distributed system."]}),"\n",(0,n.jsx)(s.p,{children:"Next, a workflow can have exceptional cases. Our e-commerce flow has special logic to handle cases when the customer's credit card was invalid or when the ordered item was out of stock."}),"\n",(0,n.jsxs)(s.p,{children:["Finally, a workflow can be ",(0,n.jsx)(s.em,{children:"asynchronous"}),", meaning that it requires waiting for input from the external world in order to complete. For example, our e-commerce workflow sometimes must wait for a customer to update their credit card information before completing."]}),"\n",(0,n.jsxs)(s.p,{children:["The mission-critical nature of workflows, combined with asynchronous events and exceptional cases, places a premium on ",(0,n.jsx)(s.em,{children:"consistency."})," The results of workflows must be predictable for customers and easy to reason about for business managers and software engineers."]}),"\n",(0,n.jsx)(s.admonition,{type:"note",children:(0,n.jsx)(s.p,{children:'A technical or business process does not need to satisfy all three characteristics to be a "workflow." In fact, simple processes with just one or two linear steps can benefit from a workflow engine.'})}),"\n",(0,n.jsx)(s.h3,{id:"workflow-engines",children:"Workflow Engines"}),"\n",(0,n.jsx)(s.p,{children:"A workflow engine is a software system that makes sure the trains run on time in your processes. To use a workflow engine, you must:"}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Define your Tasks"}),", which are units of work that can be executed in a workflow, and write ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/tasks",children:"Task Workers"})," which implement small functions or methods in code to execute those tasks."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Register a Workflow Specification"})," (we call it a ",(0,n.jsxs)(s.a,{href:"https://littlehorse.dev/docs/concepts/workflows",children:[(0,n.jsx)(s.code,{children:"WfSpec"})," in LittleHorse"]}),") which specifies what tasks to execute and when."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Run your workflow"})," so that the workflow engine can orchestrate the process to completion."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:(0,n.jsx)(s.img,{alt:"LittleHorse Architecture",src:i(459).A+"",width:"1846",height:"1019"})}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/task-worker-development",children:"Task Workers"})," are where a workflow can interface with the outside world. Since a Task in a workflow results in the LittleHorse SDK calling a programming function/method of your choosing, Task Workers allow LittleHorse to integrate with any system. Task Workers can make database queries, call external API's, provision infrastructure on AWS, send push notifications to customer mobile apps, perform calculations, call an LLM API, and more."]}),"\n",(0,n.jsxs)(s.p,{children:["In LittleHorse, the ",(0,n.jsx)(s.code,{children:"WfSpec"})," is ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/wfspec-development",children:"defined in code"})," in a language of your choice. Because LittleHorse was written with developers in mind, our DSL's have all of the primitives that you'd expect in a programming language: variables, control flow, exception handling, child threads, interrupts, and awaiting for external events. This allows workflows to be:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Easy to reason about."}),"\n",(0,n.jsx)(s.li,{children:"Tracked in version control."}),"\n",(0,n.jsx)(s.li,{children:"Familiar and easy to learn."}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Once you tell LittleHorse to ",(0,n.jsxs)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/running-workflows",children:["run an instance of your ",(0,n.jsx)(s.code,{children:"WfSpec"})]}),", LittleHorse will oversee the entire process until it completes. Failed tasks will be retried, every step will be journaled, and the state of your processes will be safely and durably persisted while waiting for external triggers."]}),"\n",(0,n.jsx)(s.h2,{id:"why-workflow",children:"Why Workflow?"}),"\n",(0,n.jsx)(s.p,{children:'Microservice applications that are designed as distributed workflows without a workflow engine (like a chain of dominoes falling) present operational challenges because there is no "leader" providing oversight over the microservice processes. Thankfully, a developer-focused and horizontally-scalable workflow engine like LittleHorse can fill the "leader" role, thus providing oversight and reliability, and taming the complexity of your business processes.'}),"\n",(0,n.jsxs)(s.p,{children:["Additionally, using a workflow engine allows you to develop a set of ",(0,n.jsx)(s.em,{children:"reusable"})," and ",(0,n.jsx)(s.em,{children:"modular"})," tasks which can be easily dropped into any business workflow with a common API. Rather than accumulating tech debt, workflow engines allow you to accumulate a set of useful lego bricks."]}),"\n",(0,n.jsxs)(s.p,{children:["In most existing organizations there's a long list of API calls required to simply ",(0,n.jsx)(s.em,{children:"run"})," a workflow. Training engineers to use all of the new APIs while securely distributing access and permissions causes confusion and slow development cycles. Workflow engines provide a single API and single system that allows anyone to securely manage, run, and operate complex workflows."]}),"\n",(0,n.jsx)(s.h3,{id:"mission-critical-oversight",children:"Mission Critical Oversight"}),"\n",(0,n.jsxs)(s.p,{children:["Mission critical business workflows leave no room for technical failures and outages. However, as we discussed ",(0,n.jsx)(s.a,{href:"/blog/challenge-of-microservices#reliability-and-correctness",children:"last week"}),", the distributed nature of microservices means that technical failures are not likely but rather certain. LittleHorse provides retries and durable execution capabilities out of the box, removing the need to create complex infrastructure for cross-service transactions (such as dead-letter queues, Outbox tables, and the SAGA pattern)."]}),"\n",(0,n.jsxs)(s.p,{children:["Additionally, mission-critical processes must be ",(0,n.jsx)(s.em,{children:"audited"})," and ",(0,n.jsx)(s.em,{children:"observed"})," in a secure manner with proper access controls. LittleHorse supports this\u2014every step in a workflow is journaled, auditable, and searchable in our dashboard. When humans execute ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"}),", you can view an audit trail of when and to whom it was assigned and executed; you can see when each ",(0,n.jsx)(s.code,{children:"TaskRun"})," started, completed, and failed (and with what inputs). Our ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/principals-and-tenants",children:"ACL's and Multi-Tenancy"}),' capabilities (and "Masked Data") ensure that the data remains accessible only to those who must see it.']}),"\n",(0,n.jsx)(s.h3,{id:"simple-asynchronous-processing",children:"Simple Asynchronous Processing"}),"\n",(0,n.jsx)(s.p,{children:"For microservice developers, handling asynchronous business processes is challenging because it forces you to persist state, correlate events, and wire together callbacks into a non-linear flow. Developers often need to create database tables for ongoing transactions and maintain complex flow diagrams showing how different services integrate with business events."}),"\n",(0,n.jsx)(s.p,{children:"However, LittleHorse provides two primitives to simplify this process:"}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/external-events",children:(0,n.jsx)(s.strong,{children:"External Events"})})," allow workflows to block until something happens in the outside world, and then resume processing immediately thereafter."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:(0,n.jsx)(s.strong,{children:"User Tasks"})})," are like External Events but they model getting input from humans. User Tasks support reminders, assignment, groups, and users."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Together, User Tasks and External Events allow developers to transform complex asynchronous flows (such as our e-commerce example when we wait for a customer to provide a new credit card) into a more manageable linear flow."}),"\n",(0,n.jsx)(s.h3,{id:"exception-handling",children:"Exception Handling"}),"\n",(0,n.jsx)(s.p,{children:"Finally, just as processes can fail at the technical level, they can also fail at the business level. As per our ongoing e-commerce example, cards can run out of funds, items go out of stock, customers can cancel orders while they are being processed."}),"\n",(0,n.jsx)(s.p,{children:'Handling any given exceptional case in a business workflow might involve actions in several different microservices. Without a workflow engine, therefore, each exceptional case results in more and more complex interdependencies in your microservices, creating the notoriously feared "Distributed Monolith."'}),"\n",(0,n.jsxs)(s.p,{children:["In contrast, with LittleHorse as your workflow orchestrator, the dependencies between microservices are mitigated and workflow concepts such as ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/workflows#failure-handling",children:"Failure Handling"})," allow you to easily define rollbacks, SAGA patterns, and edge cases without introducing further accidental complexity into your microservices. This allows startups and enterprises alike to implement robust, enterprise-grade business applications without accumulating costly technical debt."]}),"\n",(0,n.jsx)(s.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,n.jsx)(s.p,{children:"For a variety of reasons, startups and enterprises alike may need to work with microservices despite the challenges they bring. Thankfully, workflow engines like LittleHorse can mitigate those problems by providing oversight into your entire process."}),"\n",(0,n.jsx)(s.p,{children:"At the LittleHorse Council, we are very excited about the upcoming 1.0 release. Over the next few weeks, we will:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Complete additional load tests, chaos tests, and benchmarks in preparation for 1.0."}),"\n",(0,n.jsx)(s.li,{children:"Blog about how you can write an e-commerce workflow in LittleHorse with Python."}),"\n",(0,n.jsx)(s.li,{children:"Do final testing before we release!"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["And if you enjoyed this post, give us a star ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"on GitHub"})," and try out ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"our quickstarts"})," to get going with LittleHorse in under 5 minutes."]})]})}function d(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},6216:(e,s,i)=>{i.d(s,{A:()=>n});const n=i.p+"assets/images/2024-08-27-complex-checkout-915172cb1dfafc0a3e9a7cc0b042ac3a.png"},459:(e,s,i)=>{i.d(s,{A:()=>n});const n=i.p+"assets/images/2024-08-28-lh-application-8847f083fce679fe41abec88d4375125.png"},8453:(e,s,i)=>{i.d(s,{R:()=>r,x:()=>a});var n=i(6540);const o={},t=n.createContext(o);function r(e){const s=n.useContext(t);return n.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),n.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/e3618154.70c7082b.js b/assets/js/e3618154.70c7082b.js new file mode 100644 index 000000000..b4f413c09 --- /dev/null +++ b/assets/js/e3618154.70c7082b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[826],{9100:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>l,contentTitle:()=>t,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var o=i(4848),n=i(8453);const r={slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},t=void 0,a={permalink:"/blog/microservices-and-workflow",source:"@site/blog/2024-09-02-microservices-and-workflow.md",title:"Microservices and Workflow: A Match Made in Heaven",description:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.",date:"2024-09-02T00:00:00.000Z",tags:[{inline:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.575,hasTruncateMarker:!0,authors:[{name:"Colt McNealy",title:"Managing Member of the LLC",description:"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He's a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.",page:{permalink:"/blog/authors/coltmcnealy"},socials:{github:"https://github.com/coltmcnealy-lh",linkedin:"https://www.linkedin.com/in/colt-mcnealy-900b7a148/",x:"https://x.com/coltmcnealy"},imageURL:"https://avatars.githubusercontent.com/u/100447728",key:"coltmcnealy"}],frontMatter:{slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"The Basics of Workflow",permalink:"/blog/basics-of-workflow"},nextItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"}},l={authorsImageUrls:[void 0]},c=[{value:"What is a Workflow?",id:"what-is-a-workflow",level:2},{value:"Workflow Engines",id:"workflow-engines",level:3},{value:"Why Workflow?",id:"why-workflow",level:2},{value:"Mission Critical Oversight",id:"mission-critical-oversight",level:3},{value:"Simple Asynchronous Processing",id:"simple-asynchronous-processing",level:3},{value:"Exception Handling",id:"exception-handling",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,n.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.p,{children:"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices."}),"\n",(0,o.jsxs)(s.admonition,{type:"info",children:[(0,o.jsx)(s.p,{children:"This is the third and final part of a 3-part blog series:"}),(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"The Promise of Microservices"})}),"\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices",children:"The Challenge with Microservices"})}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"[This Post]"})," Workflow and Microservices: A Match Made in Heaven"]}),"\n"]})]}),"\n",(0,o.jsxs)(s.p,{children:["If you're just joining for the third blog post, we have so far established that microservices are an effective tool for allowing your engineering team to grow beyond just a handful of people working on an enterprise application. However, microservice systems are by nature ",(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservices-are-leaderless",children:(0,o.jsx)(s.strong,{children:"Leaderless"})})," and ",(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservices-are-distributed",children:(0,o.jsx)(s.strong,{children:"Distributed"})}),", which yields challenges in:"]}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#observability",children:(0,o.jsx)(s.strong,{children:"Observability"})}),","]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#reliability-and-correctness",children:(0,o.jsx)(s.strong,{children:"Reliability"})}),", and"]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#microservice-coupling",children:(0,o.jsx)(s.strong,{children:"Complexity Management"})}),"."]}),"\n"]}),"\n",(0,o.jsxs)(s.p,{children:["Those challenges inspired me to create ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts",children:"LittleHorse"})," in the fall of 2021. LittleHorse provides primitives and guardrails out of the box which make it easier to wrangle with distributed systems and coordinate processes/transactions across multiple microservices."]}),"\n",(0,o.jsx)(s.p,{children:"In this post, we'll discuss:"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["What ",(0,o.jsx)(s.em,{children:"workflow"})," means."]}),"\n",(0,o.jsx)(s.li,{children:"How LittleHorse's workflow orchestration capabilities make it easier for you to reliably orchestrate complex business processes."}),"\n"]}),"\n",(0,o.jsxs)(s.admonition,{type:"tip",children:[(0,o.jsx)(s.p,{children:"Want to give LittleHorse a try? Get in touch with us!"}),(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:["Join the ",(0,o.jsx)(s.a,{href:"https://launchpass.com/littlehorsecommunity",children:(0,o.jsx)(s.strong,{children:"LH Slack Community"})})," for the latest news and help from community experts."]}),"\n",(0,o.jsxs)(s.li,{children:["Check out our ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:(0,o.jsx)(s.strong,{children:"Getting Started"})})," page."]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform",children:(0,o.jsx)(s.strong,{children:"Say hello"})})," if you'd like to get in touch with someone from the LittleHorse Enterprises team."]}),"\n"]})]}),"\n",(0,o.jsx)(s.h2,{id:"what-is-a-workflow",children:"What is a Workflow?"}),"\n",(0,o.jsx)(s.p,{children:"A workflow is a blueprint that defines a series of tasks to be performed (perhaps conditioned on certain inputs or external events) in order to achieve a business outcome."}),"\n",(0,o.jsxs)(s.p,{children:["If you recall the e-commerce example from the ",(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#the-nature-of-microservices",children:"previous blog post"}),", you can think of the abstract checkout process as a workflow. This example is interesting because it demonstrates multiple characteristics of common business processes that make microservice development hard."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.img,{alt:"E-Commerce Checkout Process Diagram",src:i(6216).A+"",width:"1472",height:"406"})}),"\n",(0,o.jsxs)(s.p,{children:["First, a workflow can be ",(0,o.jsx)(s.em,{children:"mission critical"}),". A customer would be very unhappy if the vendor charged their credit card but failed to ship their order. In technical terms, this means that the state of a workflow needs to be consistent and durable, which is hard to achieve in a distributed system."]}),"\n",(0,o.jsx)(s.p,{children:"Next, a workflow can have exceptional cases. Our e-commerce flow has special logic to handle cases when the customer's credit card was invalid or when the ordered item was out of stock."}),"\n",(0,o.jsxs)(s.p,{children:["Finally, a workflow can be ",(0,o.jsx)(s.em,{children:"asynchronous"}),", meaning that it requires waiting for input from the external world in order to complete. For example, our e-commerce workflow sometimes must wait for a customer to update their credit card information before completing."]}),"\n",(0,o.jsxs)(s.p,{children:["The mission-critical nature of workflows, combined with asynchronous events and exceptional cases, places a premium on ",(0,o.jsx)(s.em,{children:"consistency."})," The results of workflows must be predictable for customers and easy to reason about for business managers and software engineers."]}),"\n",(0,o.jsx)(s.admonition,{type:"note",children:(0,o.jsx)(s.p,{children:'A technical or business process does not need to satisfy all three characteristics to be a "workflow." In fact, simple processes with just one or two linear steps can benefit from a workflow engine.'})}),"\n",(0,o.jsx)(s.h3,{id:"workflow-engines",children:"Workflow Engines"}),"\n",(0,o.jsx)(s.p,{children:"A workflow engine is a software system that makes sure the trains run on time in your processes. To use a workflow engine, you must:"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"Define your Tasks"}),", which are units of work that can be executed in a workflow, and write ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/tasks",children:"Task Workers"})," which implement small functions or methods in code to execute those tasks."]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"Register a Workflow Specification"})," (we call it a ",(0,o.jsxs)(s.a,{href:"https://littlehorse.dev/docs/concepts/workflows",children:[(0,o.jsx)(s.code,{children:"WfSpec"})," in LittleHorse"]}),") which specifies what tasks to execute and when."]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"Run your workflow"})," so that the workflow engine can orchestrate the process to completion."]}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.img,{alt:"LittleHorse Architecture",src:i(459).A+"",width:"1846",height:"1019"})}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/task-worker-development",children:"Task Workers"})," are where a workflow can interface with the outside world. Since a Task in a workflow results in the LittleHorse SDK calling a programming function/method of your choosing, Task Workers allow LittleHorse to integrate with any system. Task Workers can make database queries, call external API's, provision infrastructure on AWS, send push notifications to customer mobile apps, perform calculations, call an LLM API, and more."]}),"\n",(0,o.jsxs)(s.p,{children:["In LittleHorse, the ",(0,o.jsx)(s.code,{children:"WfSpec"})," is ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/wfspec-development",children:"defined in code"})," in a language of your choice. Because LittleHorse was written with developers in mind, our DSL's have all of the primitives that you'd expect in a programming language: variables, control flow, exception handling, child threads, interrupts, and awaiting for external events. This allows workflows to be:"]}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Easy to reason about."}),"\n",(0,o.jsx)(s.li,{children:"Tracked in version control."}),"\n",(0,o.jsx)(s.li,{children:"Familiar and easy to learn."}),"\n"]}),"\n",(0,o.jsxs)(s.p,{children:["Once you tell LittleHorse to ",(0,o.jsxs)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/running-workflows",children:["run an instance of your ",(0,o.jsx)(s.code,{children:"WfSpec"})]}),", LittleHorse will oversee the entire process until it completes. Failed tasks will be retried, every step will be journaled, and the state of your processes will be safely and durably persisted while waiting for external triggers."]}),"\n",(0,o.jsx)(s.h2,{id:"why-workflow",children:"Why Workflow?"}),"\n",(0,o.jsx)(s.p,{children:'Microservice applications that are designed as distributed workflows without a workflow engine (like a chain of dominoes falling) present operational challenges because there is no "leader" providing oversight over the microservice processes. Thankfully, a developer-focused and horizontally-scalable workflow engine like LittleHorse can fill the "leader" role, thus providing oversight and reliability, and taming the complexity of your business processes.'}),"\n",(0,o.jsxs)(s.p,{children:["Additionally, using a workflow engine allows you to develop a set of ",(0,o.jsx)(s.em,{children:"reusable"})," and ",(0,o.jsx)(s.em,{children:"modular"})," tasks which can be easily dropped into any business workflow with a common API. Rather than accumulating tech debt, workflow engines allow you to accumulate a set of useful lego bricks."]}),"\n",(0,o.jsxs)(s.p,{children:["In most existing organizations there's a long list of API calls required to simply ",(0,o.jsx)(s.em,{children:"run"})," a workflow. Training engineers to use all of the new APIs while securely distributing access and permissions causes confusion and slow development cycles. Workflow engines provide a single API and single system that allows anyone to securely manage, run, and operate complex workflows."]}),"\n",(0,o.jsx)(s.h3,{id:"mission-critical-oversight",children:"Mission Critical Oversight"}),"\n",(0,o.jsxs)(s.p,{children:["Mission critical business workflows leave no room for technical failures and outages. However, as we discussed ",(0,o.jsx)(s.a,{href:"/blog/challenge-of-microservices#reliability-and-correctness",children:"last week"}),", the distributed nature of microservices means that technical failures are not likely but rather certain. LittleHorse provides retries and durable execution capabilities out of the box, removing the need to create complex infrastructure for cross-service transactions (such as dead-letter queues, Outbox tables, and the SAGA pattern)."]}),"\n",(0,o.jsxs)(s.p,{children:["Additionally, mission-critical processes must be ",(0,o.jsx)(s.em,{children:"audited"})," and ",(0,o.jsx)(s.em,{children:"observed"})," in a secure manner with proper access controls. LittleHorse supports this\u2014every step in a workflow is journaled, auditable, and searchable in our dashboard. When humans execute ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"}),", you can view an audit trail of when and to whom it was assigned and executed; you can see when each ",(0,o.jsx)(s.code,{children:"TaskRun"})," started, completed, and failed (and with what inputs). Our ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/principals-and-tenants",children:"ACL's and Multi-Tenancy"}),' capabilities (and "Masked Data") ensure that the data remains accessible only to those who must see it.']}),"\n",(0,o.jsx)(s.h3,{id:"simple-asynchronous-processing",children:"Simple Asynchronous Processing"}),"\n",(0,o.jsx)(s.p,{children:"For microservice developers, handling asynchronous business processes is challenging because it forces you to persist state, correlate events, and wire together callbacks into a non-linear flow. Developers often need to create database tables for ongoing transactions and maintain complex flow diagrams showing how different services integrate with business events."}),"\n",(0,o.jsx)(s.p,{children:"However, LittleHorse provides two primitives to simplify this process:"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/external-events",children:(0,o.jsx)(s.strong,{children:"External Events"})})," allow workflows to block until something happens in the outside world, and then resume processing immediately thereafter."]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:(0,o.jsx)(s.strong,{children:"User Tasks"})})," are like External Events but they model getting input from humans. User Tasks support reminders, assignment, groups, and users."]}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:"Together, User Tasks and External Events allow developers to transform complex asynchronous flows (such as our e-commerce example when we wait for a customer to provide a new credit card) into a more manageable linear flow."}),"\n",(0,o.jsx)(s.h3,{id:"exception-handling",children:"Exception Handling"}),"\n",(0,o.jsx)(s.p,{children:"Finally, just as processes can fail at the technical level, they can also fail at the business level. As per our ongoing e-commerce example, cards can run out of funds, items go out of stock, customers can cancel orders while they are being processed."}),"\n",(0,o.jsx)(s.p,{children:'Handling any given exceptional case in a business workflow might involve actions in several different microservices. Without a workflow engine, therefore, each exceptional case results in more and more complex interdependencies in your microservices, creating the notoriously feared "Distributed Monolith."'}),"\n",(0,o.jsxs)(s.p,{children:["In contrast, with LittleHorse as your workflow orchestrator, the dependencies between microservices are mitigated and workflow concepts such as ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/workflows#failure-handling",children:"Failure Handling"})," allow you to easily define rollbacks, SAGA patterns, and edge cases without introducing further accidental complexity into your microservices. This allows startups and enterprises alike to implement robust, enterprise-grade business applications without accumulating costly technical debt."]}),"\n",(0,o.jsx)(s.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,o.jsx)(s.p,{children:"For a variety of reasons, startups and enterprises alike may need to work with microservices despite the challenges they bring. Thankfully, workflow engines like LittleHorse can mitigate those problems by providing oversight into your entire process."}),"\n",(0,o.jsx)(s.p,{children:"At the LittleHorse Council, we are very excited about the upcoming 1.0 release. Over the next few weeks, we will:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Complete additional load tests, chaos tests, and benchmarks in preparation for 1.0."}),"\n",(0,o.jsx)(s.li,{children:"Blog about how you can write an e-commerce workflow in LittleHorse with Python."}),"\n",(0,o.jsx)(s.li,{children:"Do final testing before we release!"}),"\n"]}),"\n",(0,o.jsxs)(s.p,{children:["And if you enjoyed this post, give us a star ",(0,o.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"on GitHub"})," and try out ",(0,o.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"our quickstarts"})," to get going with LittleHorse in under 5 minutes."]})]})}function d(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},6216:(e,s,i)=>{i.d(s,{A:()=>o});const o=i.p+"assets/images/2024-08-27-complex-checkout-915172cb1dfafc0a3e9a7cc0b042ac3a.png"},459:(e,s,i)=>{i.d(s,{A:()=>o});const o=i.p+"assets/images/2024-08-28-lh-application-8847f083fce679fe41abec88d4375125.png"},8453:(e,s,i)=>{i.d(s,{R:()=>t,x:()=>a});var o=i(6540);const n={},r=o.createContext(n);function t(e){const s=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:t(e.components),o.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ef8b811a.8137cb4a.js b/assets/js/ef8b811a.2e85f147.js similarity index 68% rename from assets/js/ef8b811a.8137cb4a.js rename to assets/js/ef8b811a.2e85f147.js index 384255668..5fc61a22f 100644 --- a/assets/js/ef8b811a.8137cb4a.js +++ b/assets/js/ef8b811a.2e85f147.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8947],{6600:e=>{e.exports=JSON.parse('{"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy","count":4},{"name":"The LittleHorse Council","title":"The Council of LittleHorse Maintainers","description":"LittleHorse Orchestrator is maintained by LittleHorse Enterprises LLC and available under the SSPL license. The LittleHorse Council is the group of engineers inside LittleHorse Enterprises LLC who are responsible for the stewardship of the open-source Orchestrator project and charged with looking out for the best interests of the LH Community.","url":"https://littlehorse.io","page":{"permalink":"/blog/authors/lh-council"},"socials":{"github":"https://github.com/littlehorse-enterprises","linkedin":"https://www.linkedin.com/company/littlehorse"},"imageURL":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","key":"lh_council","count":7}]}')}}]); \ No newline at end of file +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8947],{6600:e=>{e.exports=JSON.parse('{"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy","count":4},{"name":"The LittleHorse Council","title":"The Council of LittleHorse Maintainers","description":"LittleHorse Orchestrator is maintained by LittleHorse Enterprises LLC and available under the SSPL license. The LittleHorse Council is the group of engineers inside LittleHorse Enterprises LLC who are responsible for the stewardship of the open-source Orchestrator project and charged with looking out for the best interests of the LH Community.","url":"https://littlehorse.io","page":{"permalink":"/blog/authors/lh-council"},"socials":{"github":"https://github.com/littlehorse-enterprises","linkedin":"https://www.linkedin.com/company/littlehorse"},"imageURL":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","key":"lh_council","count":7},{"name":"Mitchell Henderson","title":"Principal Technical Architect","description":"Mitch is a software industry veteran experienced with Apache Kafka, Apache Cassandra, and systems modernization across a diverse set of industries including financial services, healthcare, technology, retail, and manufacturing. He prides himself in ensuring that all users of LittleHorse are successful.","page":{"permalink":"/blog/authors/mitchellh"},"socials":{"github":"https://github.com/mitchell-h","linkedin":"https://www.linkedin.com/in/mitchellghenderson/"},"imageURL":"https://avatars.githubusercontent.com/u/6223426","key":"mitchellh","count":1}]}')}}]); \ No newline at end of file diff --git a/assets/js/f81c1134.19ae1014.js b/assets/js/f81c1134.19ae1014.js deleted file mode 100644 index d504946ec..000000000 --- a/assets/js/f81c1134.19ae1014.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8130],{7735:e=>{e.exports=JSON.parse('{"archive":{"blogPosts":[{"id":"saga-pattern","metadata":{"permalink":"/blog/saga-pattern","source":"@site/blog/2024-09-24-saga-pattern.md","title":"Integration Patterns: SAGA Transactions","description":"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator.","date":"2024-09-24T00:00:00.000Z","tags":[{"inline":false,"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture."}],"readingTime":5.72,"hasTruncateMarker":true,"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy"}],"frontMatter":{"slug":"saga-pattern","authors":["coltmcnealy"],"tags":["analysis"]},"unlisted":false,"nextItem":{"title":"Microservices and Workflow: A Match Made in Heaven","permalink":"/blog/microservices-and-workflow"}},"content":"The SAGA pattern allows you to defend against data loss, dropped orders, and confused (or grumpy) customers. While useful, the SAGA pattern is tricky to get right without an orchestrator.\\n\\n\x3c!-- truncate --\x3e\\n\\n:::info\\nThis is the first part in a five-part blog series on useful Integration Patterns. This blog series will help you build real-time, responsive applications and microservices that produce predictable results and prevent the Grumpy Customer Problem.\\n\\n1. **[This Post]** SAGA Transactions\\n2. [Coming soon] The Outbox Pattern\\n3. [Coming soon] Retries and Dead-Letter Queues\\n4. [Coming soon] Callbacks and External Events\\n5. [Coming soon] Queuing and Backpressure\\n:::\\n\\n## The SAGA Pattern\\n\\nAt a technical level, the [SAGA Pattern](https://microservices.io/patterns/data/saga.html) allows you to perform distributed transactions across multiple disparate systems without 2-phase commit.\\n\\nIn plain English, it is a tool in the belt of a software engineer to prevent half-fulfilled bank transfers, hanging orders, or other failures which would result in a Grumpy Customer.\\n\\n### Use Cases\\n\\nBusiness processes often need to perform actions in two separate systems either all at once or not at all. For example, you may need to charge a customer\'s credit card, reserve inventory, and ship an item to the customer all at once or not at all. If the payment went through but shipping failed, we would see the Grumpy Customer Problem yet again.\\n\\nThe SAGA pattern is appropriate when:\\n* A business process must take action across multiple separate systems (legacy monoliths, microservices, external API\'s, etc),\\n* Each of those actions can be undone via a \\"compensation task\\", and\\n* All actions must logically happen together or not at all.\\n\\n:::tip\\nIt\'s also worth noting that a different flavor of the SAGA pattern can also be used in _long-running_ business processes. In a past job, for example, I worked on a project that implemented the SAGA pattern to handle the scheduling of home inspections. In this case, the task of finding an inspector to show up at the home and confirming a time with the homeowner needed to be performed atomically.\\n:::\\n\\n### Implementation\\n\\nWhile SAGA is very hard to implement, it\'s simple to describe:\\n\\n* Try to perform the actions across the multiple systems.\\n* If one of the actions fails, then run a _compensation_ for all previously-executed tasks.\\n\\nThe _compensation_ is simply an action that \\"undoes\\" the previous action. For example, the compensation for a payment task might be to issue a refund.\\n\\n## Case Study: Order Processing\\n\\nLet\'s take a look at a familiar use-case: an order processing workflow involving the the `inventory` service, and the `payments` service. (The `orders` service is involved implicitly.) As they would in a real world scenario, all of our services live on separate physical systems and have their own databases.\\n\\nIn this business process, we first reserve inventory for the ordered item. Next, we charge the customer\'s credit card.\\n\\nIf charging the credit card fails, then we have a problem: we\'ve reserved inventory but not sold it.\\n\\nOur services need the following functionality. In SOA, these would be endpoints; in LittleHorse, they would be `TaskDef`s:\\n* `create-order`: creates an order in the `PENDING` status.\\n* `reserve-inventory`: marks an item as no longer available for sale.\\n* `charge-payment`: charges the customer.\\n* `release-inventory`: marks an item as available for sale again.\\n* `cancel-order`: marks an order as `CANCELED`.\\n* `complete-order`: marks an order as `COMPLETED`.\\n\\n### Using Message Queues\\n\\nUsing message queues, the happy path looks like the following:\\n\\n![Architecture diagram](./2024-09-24-choreography-simple.png)\\n\\n:::note\\nThe above image assumes the _choreography_ pattern, in contrast to the _orchestrator_ pattern. The orchestrator pattern is a ton of work and involves writing something that very much resembles LittleHorse!\\n:::\\n\\n1. Orders service calls `createOrder()`.\\n2. Orders service publishes to the `reserve-inventory` queue.\\n3. Inventory service reads the message and calls `reserveInventory()`.\\n4. Inventory service publishes to the `charge-payment` queue.\\n5. Payment service charges the credit card.\\n6. Payment service publishes to the `complete-order` queue.\\n7. Orders service consumes the record and calls `completeOrder()`.\\n\\nIn just the happy path, we have strong coupling already between our services in three places, and we have three message queues to manage.\\n\\nBut now we need to release the inventory and cancel the order when the payment doesn\'t go through. So the flow looks like this:\\n\\n![Architecture Diagram](./2024-09-24-choreography-saga.png)\\n\\n1. Orders service calls `createOrder()`.\\n2. Orders service publishes to the `reserve-inventory` queue.\\n3. Inventory service reads the message and calls `reserveInventory()`.\\n4. Inventory service publishes to the `charge-payment` queue.\\n5. Payment service charges the credit card _unsuccessfully_.\\n6. Payment service publishes to the `release-inventory` queue.\\n7. Inventory service reads the record and calls `releaseInventory()`.\\n8. Inventory service publishes to the `cancel-order` queue.\\n9. Orders service consumes the record and calls `cancelOrder()`.\\n\\nNow, we have _five_ different message queues that we have to wrangle with. We can also see that the overall business flow has started to leak across all of our different services.\\n\\n:::danger\\nOne thing we are ignoring in this blog post is _reliability_: to make this setup production-ready, we would also have to ensure that updates to the internal databases of the services are atomic along with pushing messages to the message queue. We will cover that in next week\'s post (along with how LittleHorse takes care of that for you).\\n:::\\n\\n### Using LittleHorse\\n\\nUsing LittleHorse, in java, this whole workflow could look like the following. This is _real code_ that does indeed compile and replaces the need for all of the complex queueing logic we had above.\\n\\n```java\\npublic void sagaExample(WorkflowThread wf) {\\n var item = wf.addVariable(\\"item\\", STR);\\n var customer = wf.addVariable(\\"customer\\", STR);\\n var price = wf.addVariable(\\"price\\", DOUBLE);\\n var orderId = wf.addVariable(\\"order-id\\", STR);\\n\\n wf.execute(\\"create-order\\", orderId);\\n wf.execute(\\"reserve-inventory\\", item, orderId);\\n\\n NodeOutput paymentResult = wf.execute(\\"charge-payment\\", customer, price);\\n // SAGA here!!\\n wf.handleException(\\"credit-card-declined\\", handler -> {\\n handler.execute(\\"release-inventory\\", item, orderId);\\n handler.execute(\\"cancel-order\\", orderId);\\n handler.fail(\\"credit-card-declined\\", \\"Credit card was declined. Order canceled!\\");\\n });\\n\\n wf.execute(\\"complete-order\\", orderId);\\n}\\n```\\n\\nInstead of managing five message queues and five strongly-coupled integration points between microservices, all we need to do is register the workflow, define _truly_ modular tasks, and let LittleHorse take care of the rest.\\n\\n## Wrapping Up\\n\\nThe SAGA Pattern is one of five tools we will cover in this series on avoiding the Grumpy Customer Problem. It\'s simple to understand but _painfully complex_ to implement. Fortunately, LittleHorse makes it easier!\\n\\n:::note\\nA careful reader, or anyone who [reads my rants on LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7244572885179121664/), might note that in order to make the order processing workflow truly reliable, we would also need to do something like the Outbox pattern or Event Sourcing.\\n\\nThat is true, and we\'ll cover it in the next post (and you\'ll see how LittleHorse does that for you automatically!).\\n:::\\n\\n### Get Involved!\\n\\nStay tuned for the next post on the Transactional Outbox Pattern! In the meantime:\\n\\n* Try out our [Quickstarts](https://littlehorse.dev/docs/developer-guide/install)\\n* Join us [on Slack](https://launchpass.com/littlehorsecommunity)\\n* Give us a star [on GitHub](https://github.com/littlehorse-enterprises/littlehorse)!"},{"id":"microservices-and-workflow","metadata":{"permalink":"/blog/microservices-and-workflow","source":"@site/blog/2024-09-02-microservices-and-workflow.md","title":"Microservices and Workflow: A Match Made in Heaven","description":"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.","date":"2024-09-02T00:00:00.000Z","tags":[{"inline":false,"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture."}],"readingTime":7.575,"hasTruncateMarker":true,"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy"}],"frontMatter":{"slug":"microservices-and-workflow","title":"Microservices and Workflow: A Match Made in Heaven","authors":["coltmcnealy"],"tags":["analysis"]},"unlisted":false,"prevItem":{"title":"Integration Patterns: SAGA Transactions","permalink":"/blog/saga-pattern"},"nextItem":{"title":"Releasing 0.11","permalink":"/blog/littlehorse-0.11-release"}},"content":"While they are often necessary, microservices are a headache. Fortunately, the right workflow engine (such as LittleHorse) can drastically reduce the difficulty of managing microservices.\\n\\n\x3c!-- truncate --\x3e\\n\\n:::info\\nThis is the third and final part of a 3-part blog series:\\n\\n1. [The Promise of Microservices](./2024-08-22-promise-of-microservices.md)\\n2. [The Challenge with Microservices](./2024-08-27-challenges-of-microservices.md)\\n3. **[This Post]** Workflow and Microservices: A Match Made in Heaven\\n:::\\n\\nIf you\'re just joining for the third blog post, we have so far established that microservices are an effective tool for allowing your engineering team to grow beyond just a handful of people working on an enterprise application. However, microservice systems are by nature [**Leaderless**](./2024-08-27-challenges-of-microservices.md#microservices-are-leaderless) and [**Distributed**](./2024-08-27-challenges-of-microservices.md#microservices-are-distributed), which yields challenges in:\\n\\n* [**Observability**](./2024-08-27-challenges-of-microservices.md#observability),\\n* [**Reliability**](./2024-08-27-challenges-of-microservices.md#reliability-and-correctness), and\\n* [**Complexity Management**](./2024-08-27-challenges-of-microservices.md#microservice-coupling).\\n\\nThose challenges inspired me to create [LittleHorse](https://littlehorse.dev/docs/concepts) in the fall of 2021. LittleHorse provides primitives and guardrails out of the box which make it easier to wrangle with distributed systems and coordinate processes/transactions across multiple microservices.\\n\\nIn this post, we\'ll discuss:\\n\\n1. What _workflow_ means.\\n2. How LittleHorse\'s workflow orchestration capabilities make it easier for you to reliably orchestrate complex business processes.\\n\\n:::tip\\nWant to give LittleHorse a try? Get in touch with us!\\n\\n* Join the [**LH Slack Community**](https://launchpass.com/littlehorsecommunity) for the latest news and help from community experts.\\n* Check out our [**Getting Started**](https://littlehorse.dev/docs/developer-guide/install) page.\\n* [**Say hello**](https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform) if you\'d like to get in touch with someone from the LittleHorse Enterprises team.\\n:::\\n\\n## What is a Workflow?\\n\\nA workflow is a blueprint that defines a series of tasks to be performed (perhaps conditioned on certain inputs or external events) in order to achieve a business outcome.\\n\\nIf you recall the e-commerce example from the [previous blog post](./2024-08-27-challenges-of-microservices.md#the-nature-of-microservices), you can think of the abstract checkout process as a workflow. This example is interesting because it demonstrates multiple characteristics of common business processes that make microservice development hard.\\n\\n![E-Commerce Checkout Process Diagram](./2024-08-27-complex-checkout.png)\\n\\nFirst, a workflow can be _mission critical_. A customer would be very unhappy if the vendor charged their credit card but failed to ship their order. In technical terms, this means that the state of a workflow needs to be consistent and durable, which is hard to achieve in a distributed system.\\n\\nNext, a workflow can have exceptional cases. Our e-commerce flow has special logic to handle cases when the customer\'s credit card was invalid or when the ordered item was out of stock.\\n\\nFinally, a workflow can be _asynchronous_, meaning that it requires waiting for input from the external world in order to complete. For example, our e-commerce workflow sometimes must wait for a customer to update their credit card information before completing.\\n\\nThe mission-critical nature of workflows, combined with asynchronous events and exceptional cases, places a premium on _consistency._ The results of workflows must be predictable for customers and easy to reason about for business managers and software engineers.\\n\\n:::note\\nA technical or business process does not need to satisfy all three characteristics to be a \\"workflow.\\" In fact, simple processes with just one or two linear steps can benefit from a workflow engine.\\n:::\\n\\n### Workflow Engines\\n\\nA workflow engine is a software system that makes sure the trains run on time in your processes. To use a workflow engine, you must:\\n\\n1. **Define your Tasks**, which are units of work that can be executed in a workflow, and write [Task Workers](https://littlehorse.dev/docs/concepts/tasks) which implement small functions or methods in code to execute those tasks.\\n2. **Register a Workflow Specification** (we call it a [`WfSpec` in LittleHorse](https://littlehorse.dev/docs/concepts/workflows)) which specifies what tasks to execute and when.\\n3. **Run your workflow** so that the workflow engine can orchestrate the process to completion.\\n\\n![LittleHorse Architecture](../static/img/2024-08-28-lh-application.png)\\n\\n[Task Workers](https://littlehorse.dev/docs/developer-guide/task-worker-development) are where a workflow can interface with the outside world. Since a Task in a workflow results in the LittleHorse SDK calling a programming function/method of your choosing, Task Workers allow LittleHorse to integrate with any system. Task Workers can make database queries, call external API\'s, provision infrastructure on AWS, send push notifications to customer mobile apps, perform calculations, call an LLM API, and more.\\n\\nIn LittleHorse, the `WfSpec` is [defined in code](https://littlehorse.dev/docs/developer-guide/wfspec-development) in a language of your choice. Because LittleHorse was written with developers in mind, our DSL\'s have all of the primitives that you\'d expect in a programming language: variables, control flow, exception handling, child threads, interrupts, and awaiting for external events. This allows workflows to be:\\n\\n* Easy to reason about.\\n* Tracked in version control.\\n* Familiar and easy to learn.\\n\\nOnce you tell LittleHorse to [run an instance of your `WfSpec`](https://littlehorse.dev/docs/developer-guide/grpc/running-workflows), LittleHorse will oversee the entire process until it completes. Failed tasks will be retried, every step will be journaled, and the state of your processes will be safely and durably persisted while waiting for external triggers.\\n\\n## Why Workflow?\\n\\nMicroservice applications that are designed as distributed workflows without a workflow engine (like a chain of dominoes falling) present operational challenges because there is no \\"leader\\" providing oversight over the microservice processes. Thankfully, a developer-focused and horizontally-scalable workflow engine like LittleHorse can fill the \\"leader\\" role, thus providing oversight and reliability, and taming the complexity of your business processes.\\n\\nAdditionally, using a workflow engine allows you to develop a set of _reusable_ and _modular_ tasks which can be easily dropped into any business workflow with a common API. Rather than accumulating tech debt, workflow engines allow you to accumulate a set of useful lego bricks.\\n\\nIn most existing organizations there\'s a long list of API calls required to simply _run_ a workflow. Training engineers to use all of the new APIs while securely distributing access and permissions causes confusion and slow development cycles. Workflow engines provide a single API and single system that allows anyone to securely manage, run, and operate complex workflows.\\n\\n### Mission Critical Oversight\\n\\nMission critical business workflows leave no room for technical failures and outages. However, as we discussed [last week](./2024-08-27-challenges-of-microservices.md#reliability-and-correctness), the distributed nature of microservices means that technical failures are not likely but rather certain. LittleHorse provides retries and durable execution capabilities out of the box, removing the need to create complex infrastructure for cross-service transactions (such as dead-letter queues, Outbox tables, and the SAGA pattern).\\n\\nAdditionally, mission-critical processes must be _audited_ and _observed_ in a secure manner with proper access controls. LittleHorse supports this\u2014every step in a workflow is journaled, auditable, and searchable in our dashboard. When humans execute [User Tasks](https://littlehorse.dev/docs/concepts/user-tasks), you can view an audit trail of when and to whom it was assigned and executed; you can see when each `TaskRun` started, completed, and failed (and with what inputs). Our [ACL\'s and Multi-Tenancy](https://littlehorse.dev/docs/concepts/principals-and-tenants) capabilities (and \\"Masked Data\\") ensure that the data remains accessible only to those who must see it.\\n\\n### Simple Asynchronous Processing\\n\\nFor microservice developers, handling asynchronous business processes is challenging because it forces you to persist state, correlate events, and wire together callbacks into a non-linear flow. Developers often need to create database tables for ongoing transactions and maintain complex flow diagrams showing how different services integrate with business events.\\n\\nHowever, LittleHorse provides two primitives to simplify this process:\\n\\n1. [**External Events**](https://littlehorse.dev/docs/concepts/external-events) allow workflows to block until something happens in the outside world, and then resume processing immediately thereafter.\\n2. [**User Tasks**](https://littlehorse.dev/docs/concepts/user-tasks) are like External Events but they model getting input from humans. User Tasks support reminders, assignment, groups, and users.\\n\\nTogether, User Tasks and External Events allow developers to transform complex asynchronous flows (such as our e-commerce example when we wait for a customer to provide a new credit card) into a more manageable linear flow.\\n\\n### Exception Handling\\n\\nFinally, just as processes can fail at the technical level, they can also fail at the business level. As per our ongoing e-commerce example, cards can run out of funds, items go out of stock, customers can cancel orders while they are being processed.\\n\\nHandling any given exceptional case in a business workflow might involve actions in several different microservices. Without a workflow engine, therefore, each exceptional case results in more and more complex interdependencies in your microservices, creating the notoriously feared \\"Distributed Monolith.\\"\\n\\nIn contrast, with LittleHorse as your workflow orchestrator, the dependencies between microservices are mitigated and workflow concepts such as [Failure Handling](https://littlehorse.dev/docs/concepts/workflows#failure-handling) allow you to easily define rollbacks, SAGA patterns, and edge cases without introducing further accidental complexity into your microservices. This allows startups and enterprises alike to implement robust, enterprise-grade business applications without accumulating costly technical debt.\\n\\n## Conclusion\\n\\nFor a variety of reasons, startups and enterprises alike may need to work with microservices despite the challenges they bring. Thankfully, workflow engines like LittleHorse can mitigate those problems by providing oversight into your entire process.\\n\\nAt the LittleHorse Council, we are very excited about the upcoming 1.0 release. Over the next few weeks, we will:\\n* Complete additional load tests, chaos tests, and benchmarks in preparation for 1.0.\\n* Blog about how you can write an e-commerce workflow in LittleHorse with Python.\\n* Do final testing before we release!\\n\\nAnd if you enjoyed this post, give us a star [on GitHub](https://github.com/littlehorse-enterprises/littlehorse) and try out [our quickstarts](https://littlehorse.dev/docs/developer-guide/install) to get going with LittleHorse in under 5 minutes."},{"id":"littlehorse-0.11-release","metadata":{"permalink":"/blog/littlehorse-0.11-release","source":"@site/blog/2024-08-31-0.11-release.md","title":"Releasing 0.11","description":"Releasing LittleHorse `0.11`","date":"2024-08-31T00:00:00.000Z","tags":[{"inline":false,"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator."},{"inline":false,"label":"LittleHorse Releases","permalink":"/blog/tags/release/","description":"Release blogs for LittleHorse Orchestrator."}],"readingTime":2.215,"hasTruncateMarker":true,"authors":[{"name":"The LittleHorse Council","title":"The Council of LittleHorse Maintainers","description":"LittleHorse Orchestrator is maintained by LittleHorse Enterprises LLC and available under the SSPL license. The LittleHorse Council is the group of engineers inside LittleHorse Enterprises LLC who are responsible for the stewardship of the open-source Orchestrator project and charged with looking out for the best interests of the LH Community.","url":"https://littlehorse.io","page":{"permalink":"/blog/authors/lh-council"},"socials":{"github":"https://github.com/littlehorse-enterprises","linkedin":"https://www.linkedin.com/company/littlehorse"},"imageURL":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","key":"lh_council"}],"frontMatter":{"title":"Releasing 0.11","description":"Releasing LittleHorse `0.11`","slug":"littlehorse-0.11-release","authors":["lh_council"],"tags":["littlehorse","release"],"image":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Microservices and Workflow: A Match Made in Heaven","permalink":"/blog/microservices-and-workflow"},"nextItem":{"title":"The Challenge of Microservices","permalink":"/blog/challenge-of-microservices"}},"content":"The `0.11` release brings with it the ability to schedule workflows on a cron job, support for secret data, and various dashboard and SDK improvements. \x3c!-- truncate --\x3e\\n\\n## New Features\\n\\nIn addition to several new features, it\'s worth calling out that we upgraded the internal `org.apache.kafka:kafka-streams` dependency to `3.8.0`, which includes several crucial bug fixes (some of which were found by our Grumpy Maintainer, [Eduwer Camacaro](https://github.com/eduwercamacaro)).\\n\\n### Dashboard\\n\\nThe Dashboard saw several enhancements, the most important of which is the `ExternalEventDef` page, which allows users to view `ExternalEvent`s associated with an `ExternalEventDef`.\\n\\n### Scheduled Workflows\\n\\nThe `ScheduledWfRun` feature creates a schedule that runs a `WfSpec` on a cron schedule. This is useful for periodic background tasks.\\n\\n### Secret Variables\\n\\nAs of LittleHorse `0.11`, you may now mark a variable as `masked()`, which means that its value is obscured on the Dashboard and also via `lhctl get variable`.\\n\\nTo make a variable Masked, you can do the following:\\n\\n```\\nWfRunVariable myVar = wf.addVariable(\\"my-var\\", STR).masked();\\n```\\n\\nWe will also release a blog about this feature soon.\\n\\n### Saving User Task Progress\\n\\nWith the `rpc SaveUserTaskRun`, it is now possible to save the results of a `UserTaskRun` without completing it. When you do this, an `event` is added to the audit log showing who saved the `UserTaskRun` and what results were saved.\\n\\n## Release Notes and Artifacts\\n\\nYou can find the release notes and downloads on our GitHub page.\\n\\n* [**`0.11.2`**](https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2)\\n* [**`0.11.1`**](https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2)\\n* [**`0.11.0`**](https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2)\\n\\n## Upgrading\\n\\nJust as since all releases since `0.8`, there were no breaking changes to our protocol buffer API. We do not anticipate any changes with our API in the future, either. This means that old client applications will continue to work with the LH Server `0.11` and beyond.\\n\\nHowever, we refactored the Go SDK to better follow GoLang conventions, which will require code changes (but no changes to the network protocol).\\n\\n### Upgrading the Go SDK\\n\\nNow, instead of having multiple modules to import and use, there are only two:\\n\\n1. The `lhproto` module, with our GRPC clients and protobuf.\\n2. The `littlehorse` module, with everything else.\\n\\nTo add the go SDK to your project, you can run:\\n\\n```\\ngo get github.com/littlehorse-enterprises/littlehorse@v0.11.2\\n```\\n\\nThen, the imports are:\\n\\n```go\\nimport (\\n\\t\\"github.com/littlehorse-enterprises/littlehorse/sdk-go/lhproto\\"\\n\\t\\"github.com/littlehorse-enterprises/littlehorse/sdk-go/littlehorse\\"\\n)\\n```\\n\\n## What\'s Next?\\n\\nBefore committing to [Semantic Versioning](https://semver.org), we will:\\n\\n* Release our release schedule and support plan.\\n* Finish inspecting our SDK\'s for bugs and minor breaking API changes that we want to do before `1.0`.\\n* Finish our benchmarks, chaos tests, and load tests to ensure that our software meets the highest quality standards.\\n\\nWe expect to release `1.0` in early October 2024."},{"id":"challenge-of-microservices","metadata":{"permalink":"/blog/challenge-of-microservices","source":"@site/blog/2024-08-27-challenges-of-microservices.md","title":"The Challenge of Microservices","description":"Microservices are often necessary, but unfortunately they bring with them some baggage.","date":"2024-08-27T00:00:00.000Z","tags":[{"inline":false,"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture."}],"readingTime":8.93,"hasTruncateMarker":true,"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy"}],"frontMatter":{"slug":"challenge-of-microservices","title":"The Challenge of Microservices","authors":["coltmcnealy"],"tags":["analysis"]},"unlisted":false,"prevItem":{"title":"Releasing 0.11","permalink":"/blog/littlehorse-0.11-release"},"nextItem":{"title":"The Promise of Microservices","permalink":"/blog/promise-of-microservices"}},"content":"Microservices are often necessary, but unfortunately they bring with them some baggage. \x3c!-- truncate --\x3e\\n\\n:::info\\nThis is the second part of a 3-part blog series:\\n\\n1. [The Promise of Microservices](./2024-08-22-promise-of-microservices.md)\\n2. **[This Post]** The Challenge with Microservices\\n3. [Workflow and Microservices: A Match Made in Heaven](./2024-09-02-microservices-and-workflow.md)\\n:::\\n\\nLast week, I [blogged](./2024-08-22-promise-of-microservices.md) about the problems that microservices solve, and why they are not only beneficial but necessary in some cases (a good bellwether is the size of your engineering team: beyond 1 or 2 dozen engineers, you will probably start to feel some problems that can be solved with microservices).\\n\\nWhen done correctly, microservices remove several bottlenecks to scaling your business. However, even well-architected microservices bring significant _accidental complexity_.\\n\\nIn particular, microservices are:\\n\\n1. Harder to **observe** and debug.\\n2. Harder to make **reliable** in the case of infrastructure or sofware failures.\\n3. More complex to **maintain** and evolve with changing business practices.\\n\\nIn this article we will explore how the above problems arise from two key facts:\\n* Microservices are **distributed**.\\n* Microservices are **choreographed without a leader**.\\n\\n:::note\\nMicroservices bring with them additional challenges around operationalization and deployment. However, those challenges are out-of-scope for this blog post as we instead choose to focus on the challenges faced by _application development teams_ rather than operations teams.\\n:::\\n\\n## The Nature of Microservices\\n\\nAs I described in [last week\'s blog](./2024-08-22-promise-of-microservices.md):\\n\\n> The term \\"microservices\\" refers to a software architecture wherein an enterprise application comprises a collection of small, loosely coupled, and independently deployable services (these small services are called \\"microservices\\" in contrast to larger monoliths). Each microservice focuses on a specific business capability and communicates with other services over a network, typically through API\'s, streaming platforms, or message queues.\\n\\nCrucially, a single microservice implements technical logic for a specific domain, or bounded context, within the larger company. In contrast, a comprehensive business process requires interacting with technology and people across _many_ business domains. The classic example of microservices architecture, e-commerce checkout, involves at least _shipping_, _billing_, _notifications_, _inventory_, and _orders_.\\n\\nIn the rest of this blog post we will examine microservices through the the lense of e-commerce checkout flow. To start with a simple use-case, the logical flow we will consider is:\\n\\n1. When an order is placed, we create a record in a database in the `orders` service.\\n2. We then reserve inventory (and ensure that the item is in stock) in the `inventory` service.\\n3. We charge the customer using the `payments` service.\\n4. Next, we ship the item using the `shipping` service.\\n5. Finally, the `notifications` service notifies the customer that the parcel is on its way.\\n\\n![Simple e-commerce workflow](./2024-08-27-simple-checkout.png)\\n\\n### Microservices are Distributed\\n\\nRecall that each service (in the workflow diagram above, each box) is its own deployable artifact. That means that the happy-path business process described above will involve five different software systems from start-to-finish.\\n\\nIn the above workflow diagram, each arrow can be accurately interpreted in two ways:\\n1. The logical flow of the business process.\\n2. The physical flow of information between microservices, either through network RPC calls or through a message broker like Apache Kafka.\\n\\nGuess what! This means we have a distributed system by definition. As Splunk [writes in a blog post](https://www.splunk.com/en_us/blog/learn/distributed-systems.html):\\n> A distributed system is simply any environment where multiple computers or devices are working on a variety of tasks and components, all spread across a network.\\n\\nYou need to look no further than the [Fallacies of Distributed Computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing) (written by Sun Microsystems Fellow L. Peter Deutsch in 1994) to see that this means that microservices are no easy task.\\n\\n### Microservices are Leaderless\\n\\nAs we\'ve seen already, any microservice-based application is a distributed system. Some distributed systems have the concept of a _leader_, which is a special node in the system that has special responsibilities.\\n\\n:::info\\nApache Kafka is my favorite distributed system. In Apache Kafka, the _Controller_ is a special Kafka server that is responsible for deciding which partition replicas are hosted on (and led by) which brokers. If the broker who was in charge of a partition goes down, then the Controller chooses a new broker from the ISR to take its place.\\n\\nTherefore, the _Controller_ in Apache Kafka can be thought of as a _leader_.\\n:::\\n\\nWhile systems like Apache Kafka have clear leaders (for example, the _Controller_ may re-assign partition leadership if the cluster becomes too imbalanced), in a microservice-based system there is no central leader to ensure that the chips fall correctly. This is by necessity, because the separation of development concerns and lifecycles across microservices means that microservices cannot and do not have leaders.\\n\\nYou can think of our e-commerce microservice flow as a line of dominoes falling. Once the process starts, no one entity is responsible for ensuring its completion. The business workflow moves from `orders` to `inventory` to `payments` and so on. If `payments` fails for some reason (perhaps a network outage makes the Stripe API unavailable), then it\'s quite possible that the `shipping` service never finds out about the workflow.\\n\\nHowever, in real life such outcomes are not acceptable. This means that every single player in the system must:\\n\\n1. Have built-in reliability mechanisms.\\n2. Understand the preceding and subsequent steps of the business process to route traffic.\\n\\nImplementing the above slows down development, more tightly couples one services to another, increases dependencies, and makes your microservice architecture much more heavyweight.\\n\\n## The Challenges\\n\\nSo far, we have established that there are many players involved in a business process, yet there\'s no one orchestrator involved in ensuring that an ordered item is delievered to the the correct address. This yields three problems:\\n\\n1. **Reliability** in the face of infrastructure failures.\\n2. **Observability** to enable system optimization and debugging.\\n3. **Coupling** of microservices to each other makes it hard to modify the system in response to new business requirements.\\n\\n### Reliability and Correctness\\n\\nProcessing orders is a mission-critical use-case. This means that orders should always complete and never be dropped (for example, we should not charge the customer\'s credit card and not ship the product to them).\\n\\nHowever, asynchronous processing such as that which I outlined above is prone to failures. For example, if you chain microservices together with direct RPC calls, a single network partition can cause an order to get stuck. Even with a reliable message broker such as Apache Kafka or AWS SQS sitting between your microservices, a write to the message broker could fail _after_ the payment went through, still resulting in a stuck order.\\n\\nJust as communication _between_ microservices can fail, the actions performed _by_each microservice can also fail. In many cases actions performed by a microservice depend upon failure-prone external systems and API\'s. If the Stripe API is down, or if the credit card is invalid, we can\'t just stop processing the order there! We must notify the customer of what went wrong and also release the inventory that we reserved.\\n\\nThis means that microservice developers spend countless hours building out infrastructure to support:\\n* Retries\\n* Dead-Letter Queues\\n* Rate-limiting\\n* Timeouts\\n* Transactional Outbox pattern\\n* SAGA Pattern\\n\\nBack to the domino analogy, if one domino misses the next, the entire chain just stops.\\n\\n### Observability\\n\\nThe second problem with microservices is that once a process instance has started (i.e. the dominoes are falling), it is very difficult to observe what happens between steps 2 through 10. This means that multi-step processes with performance issues are hard to optimize, as there are many microservices which could be the bottleneck and it\'s hard to know which. Even worse, when a customer complains about a \\"stuck order,\\" it is difficult to find the point of failure.\\n\\nAs a result, microservice engineers spend time and money:\\n* Slogging through logs on DataDog\\n* Implementing complex distributed tracing such as Zipkin, Jaeger, or Kiali\\n* Saving the state of each process instance (in our case, the `order`) in a DB just for visibility purposes at every step\\n* Coordinating with other teams to manually understand and debug workflows.\\n\\n### Microservice Coupling\\n\\nLastly, because microservices are leaderless, each player in the end-to-end process must have hard-coded integrations with the preceding and subsequent steps. This results in:\\n\\n* **Process coupling**, wherein changing a business process results in significant code updates to rewire the message queues or RPC calls between two steps.\\n* **Schema coupling**, wherein different microservices have strong dependencies on each others\' schemas.\\n\\nMicroservices come with the promise of loose coupling; however, the unfortunate reality is that this is often not the case. As a result, teams often do have to coordinate with each other during deployments.\\n\\nTo see an example of the complexity introduced by coupling of microservices, let\'s consider what happens to our e-commerce checkout workflow when we add a few edge cases to make it more realistic:\\n\\n1. If the credit card is invalid, we request the customer to provide a new one, wait for two days, and either complete or cancel the order.\\n2. If the item is out of stock, we notify the customer who elects either to wait or cancel the order.\\n\\n![Complex Checkout Architecture](./2024-08-27-complex-checkout.png)\\n\\nIn the above diagram, each arrow represents the flow of the business process _and_ information. Each microservice must have custom logic which sends information to the right place. In essence, while we _intended_ to have modular microservices that understand only their own Bounded Context, what we have is tightly-coupled systems which must understand pretty much the entire business workflow.\\n\\nTherefore, when business requirements change, unrelated microservices end up having to change their internal implementation as well.\\n\\n## Looking Forward\\n\\nMicroservices have clear and proven benefits, and are often not just advantageous but _necessary_ in some cases. However, as we discussed today, those benefits do not come without a cost. Because microservices are inherently distributed systems, challenges such as reliability, observability, and coordination are exacerbated.\\n\\nWithout spoiling the punchline of the next blog post, these challenges are why I started LittleHorse almost three years ago. Stay tuned for a description of how a _workflow orchestrator_ can alleviate a good portion of the headaches that come along with microservices.\\n\\n### Business Analytics\\n\\nAstute readers may notice that when discussing the e-commerce checkout example, we didn\'t discuss the problem of _analytics._ We focused exclusively on online transaction processing, or ensuring that the orders are properly fulfilled and processed. However, no attention was paid to business analytics to optimize future sales!\\n\\nThis area is yet another challenge. The LittleHorse Council is working on a major feature (an output Kafka Topic with records for anytime something _interesting_ happens inside a `WfRun`) for the LH Server that will address this. Don\'t worry, we\'ll blog about it soon :wink:."},{"id":"promise-of-microservices","metadata":{"permalink":"/blog/promise-of-microservices","source":"@site/blog/2024-08-22-promise-of-microservices.md","title":"The Promise of Microservices","description":"If microservices add so much complexity, why bother with the hassle?","date":"2024-08-22T00:00:00.000Z","tags":[{"inline":false,"label":"Technical Analysis","permalink":"/blog/tags/analysis/","description":"Analysis of the current and future state of Technical Architecture."}],"readingTime":7.09,"hasTruncateMarker":true,"authors":[{"name":"Colt McNealy","title":"Managing Member of the LLC","description":"Colt is the founder of LittleHorse Enterprises and the original author of the LittleHorse Orchestrator. He\'s a passionate Apache Kafka fan and loves hockey, golf, piano, cooking, and Taekwondo.","page":{"permalink":"/blog/authors/coltmcnealy"},"socials":{"github":"https://github.com/coltmcnealy-lh","linkedin":"https://www.linkedin.com/in/colt-mcnealy-900b7a148/","x":"https://x.com/coltmcnealy"},"imageURL":"https://avatars.githubusercontent.com/u/100447728","key":"coltmcnealy"}],"frontMatter":{"slug":"promise-of-microservices","title":"The Promise of Microservices","authors":["coltmcnealy"],"tags":["analysis"]},"unlisted":false,"prevItem":{"title":"The Challenge of Microservices","permalink":"/blog/challenge-of-microservices"},"nextItem":{"title":"Releasing 0.10","permalink":"/blog/littlehorse-0.10-release"}},"content":"If microservices add so much complexity, why bother with the hassle? \x3c!-- truncate --\x3e\\n\\n:::info\\nThis is the first part of a 3-part blog series:\\n\\n1. **[This Post]** The Promise of Microservices\\n2. [The Challenge with Microservices](./2024-08-27-challenges-of-microservices.md)\\n3. [Workflow and Microservices: A Match Made in Heaven](./2024-09-02-microservices-and-workflow.md)\\n:::\\n\\n\\nWe\'ve all _heard of_ microservices, but unless you\'ve read copious amounts of Sam Newman and Adam Bellemare\'s writings, you might be wondering whether, when, and why you should adopt them. In this blog post, we will examine the halcyon land promised by microservices.\\n\\nMicroservices have been [deployed widely](https://www.simform.com/blog/microservices-examples/) across many large enterprises, most notably Netflix, Uber, Shopify, PayPal, and others. As we will discover throughout this blog series, a microservice architecture is mandatory once you reach a certain size of company, and it\'s probably overkill for a 12-person startup. The gray area inbetween is the interesting part!\\n\\n## What are Microservices?\\n\\nThe term \\"microservices\\" refers to a software architecture wherein an enterprise application comprises a collection of small, loosely coupled, and independently deployable services (these small services are called \\"microservices\\" in contrast to larger monoliths). Each microservice focuses on a specific business capability and communicates with other services over a network, typically through API\'s, streaming platforms, or message queues.\\n\\nIn practice, this means that a user interaction with an application (such as placing an order) might trigger actions that occur in _many_ small, independently-deployed software systems, such as:\\n\\n* A Notification service\\n* An Inventory Management service\\n* A Payments service\\n* An Order History service\\n\\nFrom the user (client) perspective, one request is made (generally through a Load Balancer, API Gateway, or Ingress Controller) but that request may ping-pong between multiple back-end services and may also result in future actions being scheduled asynchronously:\\n\\n![Microservices Architecture](./2024-08-22-microservices-arch.png)\\n\\nIn contrast to microservices, a _monolithic_ architecture would serve the entire \\"place order\\" request on a single deployable artifact:\\n\\n![Monolithic Architecture](./2024-08-22-monolith-arch.png)\\n\\nIn Domain Driven Design, accidental complexity refers to the unintentional complexity that you introduced to your architecture (deployments, service interactions, third-party dependencies, etc.). Rule #1 of maintaining software systems is to avoid introducing accidental complexity as much as possible.\\n\\nSimply by looking at the visuals above, microservices add a significant dose of accidental complexity to your architecture (more on this in next week\'s post!). Given that, what benefits would make up for the extra complexity introduced by microservices?\\n\\n## Why Now?\\n\\nI would be first to admit that microservices bring with them a series of headaches around cost, observability, maintenance, and ease of evolution (otherwise, I would not have founded LittleHorse Enterprises!). However, microservice architecture plays a vital role in addressing two critical trends reshaping the software development landscape today:\\n\\n* Increased digitization of companies in all business sectors (accelerated by the rise of AI).\\n* Elasticity of cloud computing.\\n\\n### Increased Digitization\\n\\nThe level of digitization expected of businesses in order to compete in the modern market has drastically increased: IT teams must build software that interfaces with an ever-expanding list of external API\'s, legacy systems, user interfaces, internal tools, and SaaS providers.\\n\\nFor example: in the early 2000\'s, it was perfectly acceptable (even _expected_) for a passenger to book airline tickets over the telephone or through a travel agency. However, such an experience would be unheard of today and would immediately hobble an airline who provided such poor digital services.\\n\\nIn addition to using automation to provide better customer services, companies are generating, processing, and analyzing massive amounts of data. For example, grocery stores with razor-thin margins analyze seasonal consumption patterns in order to optimize inventory and prevent costly food waste.\\n\\nThese trends have coincided with (or _caused_, I would argue) a proliferation in the number of 1) software developers, and 2) software tools and API\'s found within companies in all industries, leading to two new problems:\\n\\n1. Allowing large teams of software developers to productively work on an enterprise application in parallel (without stepping on each others\' toes).\\n2. Ensuring that business requirements are effectively communicated to the entire (larger) software engineering team.\\n\\n### Cloud Elasticity\\n\\nAs the importance and quantity of digital software systems exploded over the last two decades, so has the availability of nearly-infinite compute power delivered through cloud infrastructure providers such as AWS.\\n\\nThe promise of _elasticity_, or the ability to quickly spin compute resources up or down according to load and only pay for what you use, is unique to the cloud: for on-prem datacenters, spinning up new compute means buying new machines from Sun Microsystems (hopefully not Microsoft!), and scaling down compute means trying to sell them off on the secondary market. (Ask my father about how that went for a lot of people in 2001.)\\n\\nBeyond scaling up and down, elasticity enables different deployment patterns that did not exist before. Whereas pre-cloud enterprises had dedicated and centralized data-center teams who were in charge of running applications, the accessibility of cloud computing gave rise to the DevOps movement. This has empowered smaller teams of software developers to take on the task of transferring software from \\"it works on my laptop!\\" to \\"it\'s now deployed in production!\\"\\n\\n## Why Microservices?\\n\\nDespite the extra complexity it brings, the microservice architecture can more than pay for itself by ensuring organizational alignment and allowing enterprise architectures to take full advantage of the cloud\'s elasticity.\\n\\n### Organizational Alignment\\n\\nAs discussed earlier, the business problems that software engineering organizations must solve today dwarf those that were solved in the 1990\'s, and so do the software engineering teams that tackle those problems.\\n\\n:::note\\nI am not belittling the engineers of the 90\'s; the problems they solved were arguably _much harder_ than the problems we face today, and there were fewer engineers to face those problems. However, it is a fact that users expect more digital-native experiences today than they did twenty years ago.\\n:::\\n\\nBy breaking applications into smaller services, we can accomplish several important things:\\n* Break up our software engineering team into smaller teams which are each responsible for individual microservices.\\n* Allow different components of a system to be developed with separate tech stacks and released independently.\\n\\nEngineering teams of over a few dozen engineers working on the same deployable piece of software is a recipe for inefficiency. Merge conflicts, arguments over tech stack, slow \\"release trains,\\" and excessive intra-team coordination are just a few problems that arise. However, by breaking your application into smaller microservices, you can also break up your engineering organization into smaller, more efficient teams each in charge of a small number (prefably one!) of microservices.\\n\\nAs an added benefit, properly-designed microservice architectures can follow the principles of Domain Driven Design. Ideally, a single microservice corresponds to a _Bounded Context_ inside the business. This enables a small piece of the technical platform (a microservice) to be managed by a small team of software engineers, who collaborate closely with subject-matter experts and business stakeholders within a very specific domain of the business. Such close collaboration can foster better alignment between business goals and the software produced by engineering teams.\\n\\n### Moving Faster\\n\\nMicroservices can allow developers to move faster by enabling continuous delivery and independent deployment of services. In a monolithic architecture, releasing a new feature or fixing a bug typically requires redeploying the entire application. Since microservices allow smaller pieces of your application to be deployed independently, engineering teams can iterate faster and deliver incremental value to business stakeholders.\\n\\nThese positive effects are amplified by the advent of cloud computing. Since deploying a new application no longer requires buying a physical machine and plugging it into your datacenter but rather just applying a new `Deployment` and `Service` on a Kubernetes cluster, it is now truly feasible for small teams of software engineers to own their application stack from laptop-to-production (obviously, within the guardrails set by the central platform team). Furthermore, cloud computing is a pay-as-you-go (and often even pay-for-what-you-use) expense rather than an up-front cost. Therefore, the dollar cost of infrastructure required to support microservices is much lower today than it would have been before the advent of cloud computing and kubernetes.\\n\\n## Conclusion\\n\\nThe microservice architecture is not just a Twitter-driven buzzword but rather a way of designing system that has several real advantages. For most organizations with over two dozen software engineers, building applications with microservices is not an option but rather a _necessity_. However, those advantages come with a cost.\\n\\nWe will discuss those challenges in next week\'s blog post...in the meantime, though, join our [Community Slack](https://launchpass.com/littlehorsecommunity) to get the latest updates!"},{"id":"littlehorse-0.10-release","metadata":{"permalink":"/blog/littlehorse-0.10-release","source":"@site/blog/2024-07-12-0.10-release.md","title":"Releasing 0.10","description":"Releasing LittleHorse `0.10`","date":"2024-07-12T00:00:00.000Z","tags":[{"inline":false,"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator."},{"inline":false,"label":"LittleHorse Releases","permalink":"/blog/tags/release/","description":"Release blogs for LittleHorse Orchestrator."}],"readingTime":2.005,"hasTruncateMarker":true,"authors":[{"name":"The LittleHorse Council","title":"The Council of LittleHorse Maintainers","description":"LittleHorse Orchestrator is maintained by LittleHorse Enterprises LLC and available under the SSPL license. The LittleHorse Council is the group of engineers inside LittleHorse Enterprises LLC who are responsible for the stewardship of the open-source Orchestrator project and charged with looking out for the best interests of the LH Community.","url":"https://littlehorse.io","page":{"permalink":"/blog/authors/lh-council"},"socials":{"github":"https://github.com/littlehorse-enterprises","linkedin":"https://www.linkedin.com/company/littlehorse"},"imageURL":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","key":"lh_council"}],"frontMatter":{"title":"Releasing 0.10","description":"Releasing LittleHorse `0.10`","slug":"littlehorse-0.10-release","authors":["lh_council"],"tags":["littlehorse","release"],"image":"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4","hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"The Promise of Microservices","permalink":"/blog/promise-of-microservices"},"nextItem":{"title":"Releasing 0.9","permalink":"/blog/littlehorse-0.9-release"}},"content":"The `0.10` release brings with it significant performance and reliability improvements. \x3c!-- truncate --\x3e\\n\\n## New Features\\n\\n### `lhctl` Binaries and Release Notes\\n\\nThe `0.10.0` release comes with a new [Release Page](https://github.com/littlehorse-enterprises/littlehorse/releases), including `lhctl` binaries built for ARM, Intel, and Windows.\\n\\n### Reliability during Rebalances\\n\\nPR [#872](https://github.com/littlehorse-enterprises/littlehorse/pull/872) improves the reliability of LittleHorse during Kafka Streams rebalances. Previously, if a write request (eg. `rpc RunWf`) was received just before a rebalance, certain requests would \\"time out\\" from the client perspective and return a `DEADLINE_EXCEEDED` grpc error despite being properly accepted and processed by the server. This PR fixes that issue by redirecting the internal `rpc WaitForCommand` to the new destination for that command.\\n\\n### Rescue Failed Workflows\\n\\nPR [#883](https://github.com/littlehorse-enterprises/littlehorse/pull/883) allows users to restart failed `WfRun`\'s via the `lhctl rescue` command. This is similar to allowing a user to execute mutating SQL queries via a CLI like `psql`.\\n\\nWith this feature, a user can fix a buggy Task Worker implementation and then restart a failed `WfRun` and get it to execute the failed `TaskRun` again via:\\n\\n```\\nlhctl rescueYour Docusaurus site did not load properly.
\nA very common reason is a wrong site baseUrl configuration.
\nCurrent configured baseUrl = ${e} ${"/"===e?" (default value)":""}
\nWe suggest trying baseUrl =
\nYour Docusaurus site did not load properly.
\nA very common reason is a wrong site baseUrl configuration.
\nCurrent configured baseUrl = ${e} ${"/"===e?" (default value)":""}
\nWe suggest trying baseUrl =
\n