diff --git a/404.html b/404.html index f240e5ad6..abfd8771e 100644 --- a/404.html +++ b/404.html @@ -18,8 +18,8 @@ - - + +
diff --git a/assets/images/2024-09-30-user-workflow-lh-e7a8ae4a642a0a8d2c4ab2635ce7b445.png b/assets/images/2024-09-30-user-workflow-lh-e7a8ae4a642a0a8d2c4ab2635ce7b445.png new file mode 100644 index 000000000..b9ba549e8 Binary files /dev/null and b/assets/images/2024-09-30-user-workflow-lh-e7a8ae4a642a0a8d2c4ab2635ce7b445.png differ diff --git a/assets/images/2024-09-30user-workflow-outbox-717d91ae3fad27e1d67957580625ab19.png b/assets/images/2024-09-30user-workflow-outbox-717d91ae3fad27e1d67957580625ab19.png new file mode 100644 index 000000000..1b0aab981 Binary files /dev/null and b/assets/images/2024-09-30user-workflow-outbox-717d91ae3fad27e1d67957580625ab19.png differ diff --git a/assets/js/015df829.b65a5ad4.js b/assets/js/015df829.b65a5ad4.js deleted file mode 100644 index 8e01981cc..000000000 --- a/assets/js/015df829.b65a5ad4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[4372],{1393:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>t,metadata:()=>a,toc:()=>l});var r=i(4848),n=i(8453);const t={slug:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis"]},o=void 0,a={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:8.93,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:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"},nextItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"}},c={authorsImageUrls:[void 0]},l=[{value:"The Nature of Microservices",id:"the-nature-of-microservices",level:2},{value:"Microservices are Distributed",id:"microservices-are-distributed",level:3},{value:"Microservices are Leaderless",id:"microservices-are-leaderless",level:3},{value:"The Challenges",id:"the-challenges",level:2},{value:"Reliability and Correctness",id:"reliability-and-correctness",level:3},{value:"Observability",id:"observability",level:3},{value:"Microservice Coupling",id:"microservice-coupling",level:3},{value:"Looking Forward",id:"looking-forward",level:2},{value:"Business Analytics",id:"business-analytics",level:3}];function h(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",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,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.p,{children:"Microservices are often necessary, but unfortunately they bring with them some baggage. "}),"\n",(0,r.jsxs)(s.admonition,{type:"info",children:[(0,r.jsx)(s.p,{children:"This is the second part of a 3-part blog series:"}),(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"The Promise of Microservices"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"[This Post]"})," The Challenge with Microservices"]}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/blog/microservices-and-workflow",children:"Workflow and Microservices: A Match Made in Heaven"})}),"\n"]})]}),"\n",(0,r.jsxs)(s.p,{children:["Last week, I ",(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"blogged"})," 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",(0,r.jsxs)(s.p,{children:["When done correctly, microservices remove several bottlenecks to scaling your business. However, even well-architected microservices bring significant ",(0,r.jsx)(s.em,{children:"accidental complexity"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"In particular, microservices are:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Harder to ",(0,r.jsx)(s.strong,{children:"observe"})," and debug."]}),"\n",(0,r.jsxs)(s.li,{children:["Harder to make ",(0,r.jsx)(s.strong,{children:"reliable"})," in the case of infrastructure or sofware failures."]}),"\n",(0,r.jsxs)(s.li,{children:["More complex to ",(0,r.jsx)(s.strong,{children:"maintain"})," and evolve with changing business practices."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"In this article we will explore how the above problems arise from two key facts:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Microservices are ",(0,r.jsx)(s.strong,{children:"distributed"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["Microservices are ",(0,r.jsx)(s.strong,{children:"choreographed without a leader"}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.admonition,{type:"note",children:(0,r.jsxs)(s.p,{children:["Microservices 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 ",(0,r.jsx)(s.em,{children:"application development teams"})," rather than operations teams."]})}),"\n",(0,r.jsx)(s.h2,{id:"the-nature-of-microservices",children:"The Nature of Microservices"}),"\n",(0,r.jsxs)(s.p,{children:["As I described in ",(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"last week's blog"}),":"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsx)(s.p,{children:'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"]}),"\n",(0,r.jsxs)(s.p,{children:["Crucially, 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 ",(0,r.jsx)(s.em,{children:"many"})," business domains. The classic example of microservices architecture, e-commerce checkout, involves at least ",(0,r.jsx)(s.em,{children:"shipping"}),", ",(0,r.jsx)(s.em,{children:"billing"}),", ",(0,r.jsx)(s.em,{children:"notifications"}),", ",(0,r.jsx)(s.em,{children:"inventory"}),", and ",(0,r.jsx)(s.em,{children:"orders"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"In 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["When an order is placed, we create a record in a database in the ",(0,r.jsx)(s.code,{children:"orders"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["We then reserve inventory (and ensure that the item is in stock) in the ",(0,r.jsx)(s.code,{children:"inventory"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["We charge the customer using the ",(0,r.jsx)(s.code,{children:"payments"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["Next, we ship the item using the ",(0,r.jsx)(s.code,{children:"shipping"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["Finally, the ",(0,r.jsx)(s.code,{children:"notifications"})," service notifies the customer that the parcel is on its way."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Simple e-commerce workflow",src:i(9258).A+"",width:"1472",height:"141"})}),"\n",(0,r.jsx)(s.h3,{id:"microservices-are-distributed",children:"Microservices are Distributed"}),"\n",(0,r.jsx)(s.p,{children:"Recall 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",(0,r.jsx)(s.p,{children:"In the above workflow diagram, each arrow can be accurately interpreted in two ways:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"The logical flow of the business process."}),"\n",(0,r.jsx)(s.li,{children:"The physical flow of information between microservices, either through network RPC calls or through a message broker like Apache Kafka."}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Guess what! This means we have a distributed system by definition. As Splunk ",(0,r.jsx)(s.a,{href:"https://www.splunk.com/en_us/blog/learn/distributed-systems.html",children:"writes in a blog post"}),":"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsx)(s.p,{children:"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"]}),"\n",(0,r.jsxs)(s.p,{children:["You need to look no further than the ",(0,r.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing",children:"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",(0,r.jsx)(s.h3,{id:"microservices-are-leaderless",children:"Microservices are Leaderless"}),"\n",(0,r.jsxs)(s.p,{children:["As we've seen already, any microservice-based application is a distributed system. Some distributed systems have the concept of a ",(0,r.jsx)(s.em,{children:"leader"}),", which is a special node in the system that has special responsibilities."]}),"\n",(0,r.jsxs)(s.admonition,{type:"info",children:[(0,r.jsxs)(s.p,{children:["Apache Kafka is my favorite distributed system. In Apache Kafka, the ",(0,r.jsx)(s.em,{children:"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."]}),(0,r.jsxs)(s.p,{children:["Therefore, the ",(0,r.jsx)(s.em,{children:"Controller"})," in Apache Kafka can be thought of as a ",(0,r.jsx)(s.em,{children:"leader"}),"."]})]}),"\n",(0,r.jsxs)(s.p,{children:["While systems like Apache Kafka have clear leaders (for example, the ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["You 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 ",(0,r.jsx)(s.code,{children:"orders"})," to ",(0,r.jsx)(s.code,{children:"inventory"})," to ",(0,r.jsx)(s.code,{children:"payments"})," and so on. If ",(0,r.jsx)(s.code,{children:"payments"})," fails for some reason (perhaps a network outage makes the Stripe API unavailable), then it's quite possible that the ",(0,r.jsx)(s.code,{children:"shipping"})," service never finds out about the workflow."]}),"\n",(0,r.jsx)(s.p,{children:"However, in real life such outcomes are not acceptable. This means that every single player in the system must:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"Have built-in reliability mechanisms."}),"\n",(0,r.jsx)(s.li,{children:"Understand the preceding and subsequent steps of the business process to route traffic."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Implementing the above slows down development, more tightly couples one services to another, increases dependencies, and makes your microservice architecture much more heavyweight."}),"\n",(0,r.jsx)(s.h2,{id:"the-challenges",children:"The Challenges"}),"\n",(0,r.jsx)(s.p,{children:"So 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Reliability"})," in the face of infrastructure failures."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Observability"})," to enable system optimization and debugging."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Coupling"})," of microservices to each other makes it hard to modify the system in response to new business requirements."]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"reliability-and-correctness",children:"Reliability and Correctness"}),"\n",(0,r.jsx)(s.p,{children:"Processing 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",(0,r.jsxs)(s.p,{children:["However, 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 ",(0,r.jsx)(s.em,{children:"after"})," the payment went through, still resulting in a stuck order."]}),"\n",(0,r.jsxs)(s.p,{children:["Just as communication ",(0,r.jsx)(s.em,{children:"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",(0,r.jsx)(s.p,{children:"This means that microservice developers spend countless hours building out infrastructure to support:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Retries"}),"\n",(0,r.jsx)(s.li,{children:"Dead-Letter Queues"}),"\n",(0,r.jsx)(s.li,{children:"Rate-limiting"}),"\n",(0,r.jsx)(s.li,{children:"Timeouts"}),"\n",(0,r.jsx)(s.li,{children:"Transactional Outbox pattern"}),"\n",(0,r.jsx)(s.li,{children:"SAGA Pattern"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Back to the domino analogy, if one domino misses the next, the entire chain just stops."}),"\n",(0,r.jsx)(s.h3,{id:"observability",children:"Observability"}),"\n",(0,r.jsx)(s.p,{children:'The 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",(0,r.jsx)(s.p,{children:"As a result, microservice engineers spend time and money:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Slogging through logs on DataDog"}),"\n",(0,r.jsx)(s.li,{children:"Implementing complex distributed tracing such as Zipkin, Jaeger, or Kiali"}),"\n",(0,r.jsxs)(s.li,{children:["Saving the state of each process instance (in our case, the ",(0,r.jsx)(s.code,{children:"order"}),") in a DB just for visibility purposes at every step"]}),"\n",(0,r.jsx)(s.li,{children:"Coordinating with other teams to manually understand and debug workflows."}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"microservice-coupling",children:"Microservice Coupling"}),"\n",(0,r.jsx)(s.p,{children:"Lastly, 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",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Process coupling"}),", wherein changing a business process results in significant code updates to rewire the message queues or RPC calls between two steps."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Schema coupling"}),", wherein different microservices have strong dependencies on each others' schemas."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Microservices 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",(0,r.jsx)(s.p,{children:"To 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"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."}),"\n",(0,r.jsx)(s.li,{children:"If the item is out of stock, we notify the customer who elects either to wait or cancel the order."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Complex Checkout Architecture",src:i(6216).A+"",width:"1472",height:"406"})}),"\n",(0,r.jsxs)(s.p,{children:["In the above diagram, each arrow represents the flow of the business process ",(0,r.jsx)(s.em,{children:"and"})," information. Each microservice must have custom logic which sends information to the right place. In essence, while we ",(0,r.jsx)(s.em,{children:"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",(0,r.jsx)(s.p,{children:"Therefore, when business requirements change, unrelated microservices end up having to change their internal implementation as well."}),"\n",(0,r.jsx)(s.h2,{id:"looking-forward",children:"Looking Forward"}),"\n",(0,r.jsxs)(s.p,{children:["Microservices have clear and proven benefits, and are often not just advantageous but ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["Without 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 ",(0,r.jsx)(s.em,{children:"workflow orchestrator"})," can alleviate a good portion of the headaches that come along with microservices."]}),"\n",(0,r.jsx)(s.h3,{id:"business-analytics",children:"Business Analytics"}),"\n",(0,r.jsxs)(s.p,{children:["Astute readers may notice that when discussing the e-commerce checkout example, we didn't discuss the problem of ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["This area is yet another challenge. The LittleHorse Council is working on a major feature (an output Kafka Topic with records for anytime something ",(0,r.jsx)(s.em,{children:"interesting"})," happens inside a ",(0,r.jsx)(s.code,{children:"WfRun"}),") for the LH Server that will address this. Don't worry, we'll blog about it soon ","\ud83d\ude09","."]})]})}function d(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},6216:(e,s,i)=>{i.d(s,{A:()=>r});const r=i.p+"assets/images/2024-08-27-complex-checkout-915172cb1dfafc0a3e9a7cc0b042ac3a.png"},9258:(e,s,i)=>{i.d(s,{A:()=>r});const r=i.p+"assets/images/2024-08-27-simple-checkout-301d7115bb5b77f34c84342998c2921d.png"},8453:(e,s,i)=>{i.d(s,{R:()=>o,x:()=>a});var r=i(6540);const n={},t=r.createContext(n);function o(e){const s=r.useContext(t);return r.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:o(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/015df829.c4bfbae7.js b/assets/js/015df829.c4bfbae7.js new file mode 100644 index 000000000..84b9ee49f --- /dev/null +++ b/assets/js/015df829.c4bfbae7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[4372],{1393:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>t,metadata:()=>a,toc:()=>l});var r=i(4848),n=i(8453);const t={slug:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},o=void 0,a={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],readingTime:8.93,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:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},unlisted:!1,prevItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"},nextItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"}},c={authorsImageUrls:[void 0]},l=[{value:"The Nature of Microservices",id:"the-nature-of-microservices",level:2},{value:"Microservices are Distributed",id:"microservices-are-distributed",level:3},{value:"Microservices are Leaderless",id:"microservices-are-leaderless",level:3},{value:"The Challenges",id:"the-challenges",level:2},{value:"Reliability and Correctness",id:"reliability-and-correctness",level:3},{value:"Observability",id:"observability",level:3},{value:"Microservice Coupling",id:"microservice-coupling",level:3},{value:"Looking Forward",id:"looking-forward",level:2},{value:"Business Analytics",id:"business-analytics",level:3}];function h(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",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,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.p,{children:"Microservices are often necessary, but unfortunately they bring with them some baggage. "}),"\n",(0,r.jsxs)(s.admonition,{type:"info",children:[(0,r.jsx)(s.p,{children:"This is the second part of a 3-part blog series:"}),(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"The Promise of Microservices"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"[This Post]"})," The Challenge with Microservices"]}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/blog/microservices-and-workflow",children:"Workflow and Microservices: A Match Made in Heaven"})}),"\n"]})]}),"\n",(0,r.jsxs)(s.p,{children:["Last week, I ",(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"blogged"})," 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",(0,r.jsxs)(s.p,{children:["When done correctly, microservices remove several bottlenecks to scaling your business. However, even well-architected microservices bring significant ",(0,r.jsx)(s.em,{children:"accidental complexity"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"In particular, microservices are:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Harder to ",(0,r.jsx)(s.strong,{children:"observe"})," and debug."]}),"\n",(0,r.jsxs)(s.li,{children:["Harder to make ",(0,r.jsx)(s.strong,{children:"reliable"})," in the case of infrastructure or sofware failures."]}),"\n",(0,r.jsxs)(s.li,{children:["More complex to ",(0,r.jsx)(s.strong,{children:"maintain"})," and evolve with changing business practices."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"In this article we will explore how the above problems arise from two key facts:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Microservices are ",(0,r.jsx)(s.strong,{children:"distributed"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["Microservices are ",(0,r.jsx)(s.strong,{children:"choreographed without a leader"}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.admonition,{type:"note",children:(0,r.jsxs)(s.p,{children:["Microservices 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 ",(0,r.jsx)(s.em,{children:"application development teams"})," rather than operations teams."]})}),"\n",(0,r.jsx)(s.h2,{id:"the-nature-of-microservices",children:"The Nature of Microservices"}),"\n",(0,r.jsxs)(s.p,{children:["As I described in ",(0,r.jsx)(s.a,{href:"/blog/promise-of-microservices",children:"last week's blog"}),":"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsx)(s.p,{children:'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"]}),"\n",(0,r.jsxs)(s.p,{children:["Crucially, 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 ",(0,r.jsx)(s.em,{children:"many"})," business domains. The classic example of microservices architecture, e-commerce checkout, involves at least ",(0,r.jsx)(s.em,{children:"shipping"}),", ",(0,r.jsx)(s.em,{children:"billing"}),", ",(0,r.jsx)(s.em,{children:"notifications"}),", ",(0,r.jsx)(s.em,{children:"inventory"}),", and ",(0,r.jsx)(s.em,{children:"orders"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"In 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["When an order is placed, we create a record in a database in the ",(0,r.jsx)(s.code,{children:"orders"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["We then reserve inventory (and ensure that the item is in stock) in the ",(0,r.jsx)(s.code,{children:"inventory"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["We charge the customer using the ",(0,r.jsx)(s.code,{children:"payments"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["Next, we ship the item using the ",(0,r.jsx)(s.code,{children:"shipping"})," service."]}),"\n",(0,r.jsxs)(s.li,{children:["Finally, the ",(0,r.jsx)(s.code,{children:"notifications"})," service notifies the customer that the parcel is on its way."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Simple e-commerce workflow",src:i(9258).A+"",width:"1472",height:"141"})}),"\n",(0,r.jsx)(s.h3,{id:"microservices-are-distributed",children:"Microservices are Distributed"}),"\n",(0,r.jsx)(s.p,{children:"Recall 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",(0,r.jsx)(s.p,{children:"In the above workflow diagram, each arrow can be accurately interpreted in two ways:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"The logical flow of the business process."}),"\n",(0,r.jsx)(s.li,{children:"The physical flow of information between microservices, either through network RPC calls or through a message broker like Apache Kafka."}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Guess what! This means we have a distributed system by definition. As Splunk ",(0,r.jsx)(s.a,{href:"https://www.splunk.com/en_us/blog/learn/distributed-systems.html",children:"writes in a blog post"}),":"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsx)(s.p,{children:"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"]}),"\n",(0,r.jsxs)(s.p,{children:["You need to look no further than the ",(0,r.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing",children:"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",(0,r.jsx)(s.h3,{id:"microservices-are-leaderless",children:"Microservices are Leaderless"}),"\n",(0,r.jsxs)(s.p,{children:["As we've seen already, any microservice-based application is a distributed system. Some distributed systems have the concept of a ",(0,r.jsx)(s.em,{children:"leader"}),", which is a special node in the system that has special responsibilities."]}),"\n",(0,r.jsxs)(s.admonition,{type:"info",children:[(0,r.jsxs)(s.p,{children:["Apache Kafka is my favorite distributed system. In Apache Kafka, the ",(0,r.jsx)(s.em,{children:"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."]}),(0,r.jsxs)(s.p,{children:["Therefore, the ",(0,r.jsx)(s.em,{children:"Controller"})," in Apache Kafka can be thought of as a ",(0,r.jsx)(s.em,{children:"leader"}),"."]})]}),"\n",(0,r.jsxs)(s.p,{children:["While systems like Apache Kafka have clear leaders (for example, the ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["You 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 ",(0,r.jsx)(s.code,{children:"orders"})," to ",(0,r.jsx)(s.code,{children:"inventory"})," to ",(0,r.jsx)(s.code,{children:"payments"})," and so on. If ",(0,r.jsx)(s.code,{children:"payments"})," fails for some reason (perhaps a network outage makes the Stripe API unavailable), then it's quite possible that the ",(0,r.jsx)(s.code,{children:"shipping"})," service never finds out about the workflow."]}),"\n",(0,r.jsx)(s.p,{children:"However, in real life such outcomes are not acceptable. This means that every single player in the system must:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"Have built-in reliability mechanisms."}),"\n",(0,r.jsx)(s.li,{children:"Understand the preceding and subsequent steps of the business process to route traffic."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Implementing the above slows down development, more tightly couples one services to another, increases dependencies, and makes your microservice architecture much more heavyweight."}),"\n",(0,r.jsx)(s.h2,{id:"the-challenges",children:"The Challenges"}),"\n",(0,r.jsx)(s.p,{children:"So 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Reliability"})," in the face of infrastructure failures."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Observability"})," to enable system optimization and debugging."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Coupling"})," of microservices to each other makes it hard to modify the system in response to new business requirements."]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"reliability-and-correctness",children:"Reliability and Correctness"}),"\n",(0,r.jsx)(s.p,{children:"Processing 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",(0,r.jsxs)(s.p,{children:["However, 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 ",(0,r.jsx)(s.em,{children:"after"})," the payment went through, still resulting in a stuck order."]}),"\n",(0,r.jsxs)(s.p,{children:["Just as communication ",(0,r.jsx)(s.em,{children:"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",(0,r.jsx)(s.p,{children:"This means that microservice developers spend countless hours building out infrastructure to support:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Retries"}),"\n",(0,r.jsx)(s.li,{children:"Dead-Letter Queues"}),"\n",(0,r.jsx)(s.li,{children:"Rate-limiting"}),"\n",(0,r.jsx)(s.li,{children:"Timeouts"}),"\n",(0,r.jsx)(s.li,{children:"Transactional Outbox pattern"}),"\n",(0,r.jsx)(s.li,{children:"SAGA Pattern"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Back to the domino analogy, if one domino misses the next, the entire chain just stops."}),"\n",(0,r.jsx)(s.h3,{id:"observability",children:"Observability"}),"\n",(0,r.jsx)(s.p,{children:'The 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",(0,r.jsx)(s.p,{children:"As a result, microservice engineers spend time and money:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Slogging through logs on DataDog"}),"\n",(0,r.jsx)(s.li,{children:"Implementing complex distributed tracing such as Zipkin, Jaeger, or Kiali"}),"\n",(0,r.jsxs)(s.li,{children:["Saving the state of each process instance (in our case, the ",(0,r.jsx)(s.code,{children:"order"}),") in a DB just for visibility purposes at every step"]}),"\n",(0,r.jsx)(s.li,{children:"Coordinating with other teams to manually understand and debug workflows."}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"microservice-coupling",children:"Microservice Coupling"}),"\n",(0,r.jsx)(s.p,{children:"Lastly, 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",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Process coupling"}),", wherein changing a business process results in significant code updates to rewire the message queues or RPC calls between two steps."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Schema coupling"}),", wherein different microservices have strong dependencies on each others' schemas."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Microservices 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",(0,r.jsx)(s.p,{children:"To 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",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsx)(s.li,{children:"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."}),"\n",(0,r.jsx)(s.li,{children:"If the item is out of stock, we notify the customer who elects either to wait or cancel the order."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Complex Checkout Architecture",src:i(6216).A+"",width:"1472",height:"406"})}),"\n",(0,r.jsxs)(s.p,{children:["In the above diagram, each arrow represents the flow of the business process ",(0,r.jsx)(s.em,{children:"and"})," information. Each microservice must have custom logic which sends information to the right place. In essence, while we ",(0,r.jsx)(s.em,{children:"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",(0,r.jsx)(s.p,{children:"Therefore, when business requirements change, unrelated microservices end up having to change their internal implementation as well."}),"\n",(0,r.jsx)(s.h2,{id:"looking-forward",children:"Looking Forward"}),"\n",(0,r.jsxs)(s.p,{children:["Microservices have clear and proven benefits, and are often not just advantageous but ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["Without 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 ",(0,r.jsx)(s.em,{children:"workflow orchestrator"})," can alleviate a good portion of the headaches that come along with microservices."]}),"\n",(0,r.jsx)(s.h3,{id:"business-analytics",children:"Business Analytics"}),"\n",(0,r.jsxs)(s.p,{children:["Astute readers may notice that when discussing the e-commerce checkout example, we didn't discuss the problem of ",(0,r.jsx)(s.em,{children:"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",(0,r.jsxs)(s.p,{children:["This area is yet another challenge. The LittleHorse Council is working on a major feature (an output Kafka Topic with records for anytime something ",(0,r.jsx)(s.em,{children:"interesting"})," happens inside a ",(0,r.jsx)(s.code,{children:"WfRun"}),") for the LH Server that will address this. Don't worry, we'll blog about it soon ","\ud83d\ude09","."]})]})}function d(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},6216:(e,s,i)=>{i.d(s,{A:()=>r});const r=i.p+"assets/images/2024-08-27-complex-checkout-915172cb1dfafc0a3e9a7cc0b042ac3a.png"},9258:(e,s,i)=>{i.d(s,{A:()=>r});const r=i.p+"assets/images/2024-08-27-simple-checkout-301d7115bb5b77f34c84342998c2921d.png"},8453:(e,s,i)=>{i.d(s,{R:()=>o,x:()=>a});var r=i(6540);const n={},t=r.createContext(n);function o(e){const s=r.useContext(t);return r.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:o(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0fad6d0a.a80b6081.js b/assets/js/0fad6d0a.41f42246.js similarity index 86% rename from assets/js/0fad6d0a.a80b6081.js rename to assets/js/0fad6d0a.41f42246.js index 865564313..3471521ad 100644 --- a/assets/js/0fad6d0a.a80b6081.js +++ b/assets/js/0fad6d0a.41f42246.js @@ -1 +1 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1173],{7700:e=>{e.exports=JSON.parse('{"author":{"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},"listMetadata":{"permalink":"/blog/authors/coltmcnealy","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([[1173],{7700:e=>{e.exports=JSON.parse('{"author":{"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":5},"listMetadata":{"permalink":"/blog/authors/coltmcnealy","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/1d85cfc3.0d0547be.js b/assets/js/1d85cfc3.0d0547be.js deleted file mode 100644 index 94a000da9..000000000 --- a/assets/js/1d85cfc3.0d0547be.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[9964],{6172:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>a,contentTitle:()=>n,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>c});var i=s(4848),r=s(8453);const o={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:!1},n=void 0,l={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:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.005,hasTruncateMarker:!0,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:!1},unlisted:!1,prevItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"},nextItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"}},a={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:"0.10"})," release brings with it significant performance and reliability improvements."]})}function u(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>n,x:()=>l});var i=s(6540);const r={},o=i.createContext(r);function n(e){const t=i.useContext(o);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:n(e.components),i.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1d85cfc3.e965be8b.js b/assets/js/1d85cfc3.e965be8b.js new file mode 100644 index 000000000..aa86bad2c --- /dev/null +++ b/assets/js/1d85cfc3.e965be8b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[9964],{6172:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>a,contentTitle:()=>n,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var i=s(4848),o=s(8453);const r={title:"Releasing 0.10",description:"Releasing LittleHorse `0.10`",slug:"littlehorse-0.10-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},n=void 0,l={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:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.005,hasTruncateMarker:!0,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:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"},nextItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"}},a={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,o.R)(),...e.components};return(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:"0.10"})," release brings with it significant performance and reliability improvements."]})}function u(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>n,x:()=>l});var i=s(6540);const o={},r=i.createContext(o);function n(e){const t=i.useContext(r);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:n(e.components),i.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/209570fd.a04cc24b.js b/assets/js/209570fd.a04cc24b.js new file mode 100644 index 000000000..8261d9792 --- /dev/null +++ b/assets/js/209570fd.a04cc24b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[312],{5321:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>u,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var o=s(4848),r=s(8453);const i={title:"Releasing 0.11",description:"Releasing LittleHorse `0.11`",slug:"littlehorse-0.11-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},n=void 0,a={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:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.215,hasTruncateMarker:!0,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:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,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"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["The ",(0,o.jsx)(t.code,{children:"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."]})}function u(e={}){const{wrapper:t}={...(0,r.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:()=>n,x:()=>a});var o=s(6540);const r={},i=o.createContext(r);function n(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(r):e.components||r:n(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/209570fd.bf4c9ae0.js b/assets/js/209570fd.bf4c9ae0.js deleted file mode 100644 index 58e217da4..000000000 --- a/assets/js/209570fd.bf4c9ae0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[312],{5321:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>u,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var o=s(4848),r=s(8453);const i={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:!1},n=void 0,a={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:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.215,hasTruncateMarker:!0,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:!1},unlisted:!1,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"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["The ",(0,o.jsx)(t.code,{children:"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."]})}function u(e={}){const{wrapper:t}={...(0,r.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:()=>n,x:()=>a});var o=s(6540);const r={},i=o.createContext(r);function n(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(r):e.components||r:n(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/23f588f5.2b39ce0a.js b/assets/js/23f588f5.2b39ce0a.js deleted file mode 100644 index 60d13f93a..000000000 --- a/assets/js/23f588f5.2b39ce0a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7434],{4299:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>a,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var n=t(4848),r=t(8453);const i={title:"Releasing 0.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},l=void 0,o={permalink:"/blog/littlehorse-0.2.0-release",source:"@site/blog/2023-08-30-0.2.0-release.md",title:"Releasing 0.2.0",description:"Making workflow development easy again.",date:"2023-08-30T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.54,hasTruncateMarker:!0,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.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},a={authorsImageUrls:[void 0]},c=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"User Tasks",id:"user-tasks",level:3},{value:"Workflow Threading",id:"workflow-threading",level:3},{value:"Python Support",id:"python-support",level:3},{value:"Security",id:"security",level:3},{value:"Performance",id:"performance",level:3},{value:"Go Support",id:"go-support",level:3},{value:"What's Next",id:"whats-next",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(s.p,{children:["We are excited to announce the release of ",(0,n.jsx)(s.code,{children:"0.2.0"}),"! In this release, we added several new features, highlighted by User Tasks, security, and Python support."]}),"\n",(0,n.jsx)(s.h2,{id:"get-started",children:"Get Started"}),"\n",(0,n.jsx)(s.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,n.jsx)(s.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Visit us on ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises",children:"GitHub"})]}),"\n",(0,n.jsxs)(s.li,{children:["Try our ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install#installation-and-quickstart",children:"quickstarts"})]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Additionally, with version ",(0,n.jsx)(s.code,{children:"0.2.0"}),", we have released our first two Docker Images:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://gallery.ecr.aws/littlehorse/littlehorse-server",children:(0,n.jsx)(s.code,{children:"lh-server"})}),", the production-ready build of the LittleHorse Server."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://gallery.ecr.aws/littlehorse/littlehorse-standalone",children:(0,n.jsx)(s.code,{children:"lh-standalone"})}),", a self-contained build of the LittleHorse Server that you can run to get a working LH Installation for local development."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"new-features",children:"New Features"}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.2.0"})," contains many exciting new features, and we've highlighted a few here."]}),"\n",(0,n.jsx)(s.h3,{id:"user-tasks",children:"User Tasks"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"})," are a massive new feature released in ",(0,n.jsx)(s.code,{children:"0.2.0"})," which allow you to schedule tasks to be executed by a human user alongside tasks that are executed by computers."]}),"\n",(0,n.jsxs)(s.p,{children:["In ",(0,n.jsx)(s.code,{children:"0.2.0"}),", User Tasks have reached stability, meaning that future releases will be backwards-compatible with the current User Tasks API. We currently have the following features:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Assignment of tasks to a User or User Group"}),"\n",(0,n.jsxs)(s.li,{children:["Reminder Tasks, or ",(0,n.jsx)(s.code,{children:"TaskRun"}),"'s that are scheduled some time after a ",(0,n.jsx)(s.code,{children:"UserTaskRun"})," is scheduled."]}),"\n",(0,n.jsxs)(s.li,{children:["Automatic reassignment of a ",(0,n.jsx)(s.code,{children:"UserTaskRun"})," after some period of inactivity."]}),"\n",(0,n.jsxs)(s.li,{children:["Manual reassignment of a ",(0,n.jsx)(s.code,{children:"UserTaskRun"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"UserTaskRun"})," search."]}),"\n"]}),"\n",(0,n.jsxs)(s.admonition,{type:"note",children:[(0,n.jsxs)(s.p,{children:["The public API for User Tasks is stable in all of the grpc clients and in the Java ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK."]}),(0,n.jsxs)(s.p,{children:["The Go and Python grpc clients both support User Tasks. However, neither Python nor Go yet have support for User Tasks in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK."]})]}),"\n",(0,n.jsx)(s.h3,{id:"workflow-threading",children:"Workflow Threading"}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.2.0"})," allows you to use a ",(0,n.jsx)(s.code,{children:"WAIT_FOR_THREADS"})," node to wait for more than one child thread at one time. For an example, see our ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/examples/parallel-approval",children:"Parallel Approval Example"})," on our GitHub."]}),"\n",(0,n.jsxs)(s.p,{children:["Future releases will provide ",(0,n.jsx)(s.em,{children:"backwards-compatible"})," enhancements to this\nfunctionality, allowing various strategies for handling failures of individual child threads."]}),"\n",(0,n.jsx)(s.h3,{id:"python-support",children:"Python Support"}),"\n",(0,n.jsxs)(s.p,{children:["We have released an alpha ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/sdk-python",children:"Python SDK"}),"! This release contains:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Python client in grpc"}),"\n",(0,n.jsx)(s.li,{children:"Python Task Worker SDK"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Currently, building ",(0,n.jsx)(s.code,{children:"WfSpec"}),"'s in Python is not supported. We aim to move python Task Worker support from alpha to beta, and add alpha support for ",(0,n.jsx)(s.code,{children:"WfSpec"})," development in python, in the ",(0,n.jsx)(s.code,{children:"0.3.0"})," release."]}),"\n",(0,n.jsxs)(s.p,{children:["To try out our python task worker client, you can head to ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"Installation Docs"})," and the ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/task-worker-development",children:"Task Worker Development Docs"}),"."]}),"\n",(0,n.jsx)(s.admonition,{type:"note",children:(0,n.jsx)(s.p,{children:"The Python SDK is in the alpha stage, meaning that future releases could break backwards compatibility."})}),"\n",(0,n.jsx)(s.h3,{id:"security",children:"Security"}),"\n",(0,n.jsxs)(s.p,{children:["We added beta support for OAuth, TLS, and mTLS in release ",(0,n.jsx)(s.code,{children:"0.2.0"}),'. The following features graduated to "beta" in this release:']}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"TLS encryption for incoming connections on all listeners, configured on a per-listener basis."}),"\n",(0,n.jsx)(s.li,{children:"mTLS to authenticate incoming connections on any listeners, configured on a per-listener basis."}),"\n",(0,n.jsx)(s.li,{children:"OAuth to authenticate incoming connections on any public listener (excluding the inter-server communication port)."}),"\n"]}),"\n",(0,n.jsxs)(s.admonition,{type:"info",children:[(0,n.jsxs)(s.p,{children:["Beta support means that we will soon add significant functionality, and as such a future release ",(0,n.jsx)(s.em,{children:"might"})," break backwards compatibility."]}),(0,n.jsxs)(s.p,{children:["However, future releases of a feature in the ",(0,n.jsx)(s.em,{children:"beta"})," state will most likely be backwards compatible with ",(0,n.jsx)(s.code,{children:"0.2.0"})," barring exceptional circumstances."]})]}),"\n",(0,n.jsx)(s.h3,{id:"performance",children:"Performance"}),"\n",(0,n.jsxs)(s.p,{children:["We made several optimizations to our storage management sub-system, reducing the number of put's and get's into our backing state store by roughly 30%. As a result, a LittleHorse Server running with a single partition is capable of scheduling over 1,100 ",(0,n.jsx)(s.code,{children:"TaskRun"}),"'s per second."]}),"\n",(0,n.jsx)(s.h3,{id:"go-support",children:"Go Support"}),"\n",(0,n.jsx)(s.p,{children:"Support for the Go client is now beta. Future releases will maintain compatibility for all features on our documentation."}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.3.0"})," will close the gap between the Java and Go SDK's, adding features such as:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Format Strings for Variable Assignments in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n",(0,n.jsxs)(s.li,{children:["User Task support in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n",(0,n.jsxs)(s.li,{children:["Configuring Indexes on ",(0,n.jsx)(s.code,{children:"Variable"}),"s in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,n.jsx)(s.p,{children:"We have several exciting features coming soon over the next few releases, including:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Fine-grained access controls"}),"\n",(0,n.jsxs)(s.li,{children:["Backward-compatible improvements to ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/exception-handling",children:"Failure Handling"})]}),"\n",(0,n.jsx)(s.li,{children:"C# support"}),"\n",(0,n.jsxs)(s.li,{children:["Python support for building ",(0,n.jsx)(s.code,{children:"WfSpec"}),"s"]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["For an enterprise-ready distribution of LittleHorse running in your own datacenter, contact ",(0,n.jsx)(s.code,{children:"sales@littlehorse.io"})," to inquire about LittleHorse Platform."]}),"\n",(0,n.jsxs)(s.p,{children:["For a pay-as-you-go, serverless Managed Service of LittleHorse in the cloud, fill out the ",(0,n.jsx)(s.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform",children:"LH Cloud Waitlist Form"}),"."]})]})}function d(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},8453:(e,s,t)=>{t.d(s,{R:()=>l,x:()=>o});var n=t(6540);const r={},i=n.createContext(r);function l(e){const s=n.useContext(i);return n.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:l(e.components),n.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/23f588f5.3c383517.js b/assets/js/23f588f5.3c383517.js new file mode 100644 index 000000000..c6b38a5e1 --- /dev/null +++ b/assets/js/23f588f5.3c383517.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7434],{4299:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>a,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var n=t(4848),r=t(8453);const i={title:"Releasing 0.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},l=void 0,o={permalink:"/blog/littlehorse-0.2.0-release",source:"@site/blog/2023-08-30-0.2.0-release.md",title:"Releasing 0.2.0",description:"Making workflow development easy again.",date:"2023-08-30T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.54,hasTruncateMarker:!0,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.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},a={authorsImageUrls:[void 0]},c=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"User Tasks",id:"user-tasks",level:3},{value:"Workflow Threading",id:"workflow-threading",level:3},{value:"Python Support",id:"python-support",level:3},{value:"Security",id:"security",level:3},{value:"Performance",id:"performance",level:3},{value:"Go Support",id:"go-support",level:3},{value:"What's Next",id:"whats-next",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(s.p,{children:["We are excited to announce the release of ",(0,n.jsx)(s.code,{children:"0.2.0"}),"! In this release, we added several new features, highlighted by User Tasks, security, and Python support."]}),"\n",(0,n.jsx)(s.h2,{id:"get-started",children:"Get Started"}),"\n",(0,n.jsx)(s.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,n.jsx)(s.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Visit us on ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises",children:"GitHub"})]}),"\n",(0,n.jsxs)(s.li,{children:["Try our ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install#installation-and-quickstart",children:"quickstarts"})]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Additionally, with version ",(0,n.jsx)(s.code,{children:"0.2.0"}),", we have released our first two Docker Images:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://gallery.ecr.aws/littlehorse/littlehorse-server",children:(0,n.jsx)(s.code,{children:"lh-server"})}),", the production-ready build of the LittleHorse Server."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://gallery.ecr.aws/littlehorse/littlehorse-standalone",children:(0,n.jsx)(s.code,{children:"lh-standalone"})}),", a self-contained build of the LittleHorse Server that you can run to get a working LH Installation for local development."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"new-features",children:"New Features"}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.2.0"})," contains many exciting new features, and we've highlighted a few here."]}),"\n",(0,n.jsx)(s.h3,{id:"user-tasks",children:"User Tasks"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/user-tasks",children:"User Tasks"})," are a massive new feature released in ",(0,n.jsx)(s.code,{children:"0.2.0"})," which allow you to schedule tasks to be executed by a human user alongside tasks that are executed by computers."]}),"\n",(0,n.jsxs)(s.p,{children:["In ",(0,n.jsx)(s.code,{children:"0.2.0"}),", User Tasks have reached stability, meaning that future releases will be backwards-compatible with the current User Tasks API. We currently have the following features:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Assignment of tasks to a User or User Group"}),"\n",(0,n.jsxs)(s.li,{children:["Reminder Tasks, or ",(0,n.jsx)(s.code,{children:"TaskRun"}),"'s that are scheduled some time after a ",(0,n.jsx)(s.code,{children:"UserTaskRun"})," is scheduled."]}),"\n",(0,n.jsxs)(s.li,{children:["Automatic reassignment of a ",(0,n.jsx)(s.code,{children:"UserTaskRun"})," after some period of inactivity."]}),"\n",(0,n.jsxs)(s.li,{children:["Manual reassignment of a ",(0,n.jsx)(s.code,{children:"UserTaskRun"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"UserTaskRun"})," search."]}),"\n"]}),"\n",(0,n.jsxs)(s.admonition,{type:"note",children:[(0,n.jsxs)(s.p,{children:["The public API for User Tasks is stable in all of the grpc clients and in the Java ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK."]}),(0,n.jsxs)(s.p,{children:["The Go and Python grpc clients both support User Tasks. However, neither Python nor Go yet have support for User Tasks in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK."]})]}),"\n",(0,n.jsx)(s.h3,{id:"workflow-threading",children:"Workflow Threading"}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.2.0"})," allows you to use a ",(0,n.jsx)(s.code,{children:"WAIT_FOR_THREADS"})," node to wait for more than one child thread at one time. For an example, see our ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/examples/parallel-approval",children:"Parallel Approval Example"})," on our GitHub."]}),"\n",(0,n.jsxs)(s.p,{children:["Future releases will provide ",(0,n.jsx)(s.em,{children:"backwards-compatible"})," enhancements to this\nfunctionality, allowing various strategies for handling failures of individual child threads."]}),"\n",(0,n.jsx)(s.h3,{id:"python-support",children:"Python Support"}),"\n",(0,n.jsxs)(s.p,{children:["We have released an alpha ",(0,n.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/sdk-python",children:"Python SDK"}),"! This release contains:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Python client in grpc"}),"\n",(0,n.jsx)(s.li,{children:"Python Task Worker SDK"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Currently, building ",(0,n.jsx)(s.code,{children:"WfSpec"}),"'s in Python is not supported. We aim to move python Task Worker support from alpha to beta, and add alpha support for ",(0,n.jsx)(s.code,{children:"WfSpec"})," development in python, in the ",(0,n.jsx)(s.code,{children:"0.3.0"})," release."]}),"\n",(0,n.jsxs)(s.p,{children:["To try out our python task worker client, you can head to ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"Installation Docs"})," and the ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/developer-guide/task-worker-development",children:"Task Worker Development Docs"}),"."]}),"\n",(0,n.jsx)(s.admonition,{type:"note",children:(0,n.jsx)(s.p,{children:"The Python SDK is in the alpha stage, meaning that future releases could break backwards compatibility."})}),"\n",(0,n.jsx)(s.h3,{id:"security",children:"Security"}),"\n",(0,n.jsxs)(s.p,{children:["We added beta support for OAuth, TLS, and mTLS in release ",(0,n.jsx)(s.code,{children:"0.2.0"}),'. The following features graduated to "beta" in this release:']}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"TLS encryption for incoming connections on all listeners, configured on a per-listener basis."}),"\n",(0,n.jsx)(s.li,{children:"mTLS to authenticate incoming connections on any listeners, configured on a per-listener basis."}),"\n",(0,n.jsx)(s.li,{children:"OAuth to authenticate incoming connections on any public listener (excluding the inter-server communication port)."}),"\n"]}),"\n",(0,n.jsxs)(s.admonition,{type:"info",children:[(0,n.jsxs)(s.p,{children:["Beta support means that we will soon add significant functionality, and as such a future release ",(0,n.jsx)(s.em,{children:"might"})," break backwards compatibility."]}),(0,n.jsxs)(s.p,{children:["However, future releases of a feature in the ",(0,n.jsx)(s.em,{children:"beta"})," state will most likely be backwards compatible with ",(0,n.jsx)(s.code,{children:"0.2.0"})," barring exceptional circumstances."]})]}),"\n",(0,n.jsx)(s.h3,{id:"performance",children:"Performance"}),"\n",(0,n.jsxs)(s.p,{children:["We made several optimizations to our storage management sub-system, reducing the number of put's and get's into our backing state store by roughly 30%. As a result, a LittleHorse Server running with a single partition is capable of scheduling over 1,100 ",(0,n.jsx)(s.code,{children:"TaskRun"}),"'s per second."]}),"\n",(0,n.jsx)(s.h3,{id:"go-support",children:"Go Support"}),"\n",(0,n.jsx)(s.p,{children:"Support for the Go client is now beta. Future releases will maintain compatibility for all features on our documentation."}),"\n",(0,n.jsxs)(s.p,{children:["Release ",(0,n.jsx)(s.code,{children:"0.3.0"})," will close the gap between the Java and Go SDK's, adding features such as:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Format Strings for Variable Assignments in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n",(0,n.jsxs)(s.li,{children:["User Task support in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n",(0,n.jsxs)(s.li,{children:["Configuring Indexes on ",(0,n.jsx)(s.code,{children:"Variable"}),"s in the ",(0,n.jsx)(s.code,{children:"WfSpec"})," SDK"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,n.jsx)(s.p,{children:"We have several exciting features coming soon over the next few releases, including:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Fine-grained access controls"}),"\n",(0,n.jsxs)(s.li,{children:["Backward-compatible improvements to ",(0,n.jsx)(s.a,{href:"https://littlehorse.dev/docs/concepts/exception-handling",children:"Failure Handling"})]}),"\n",(0,n.jsx)(s.li,{children:"C# support"}),"\n",(0,n.jsxs)(s.li,{children:["Python support for building ",(0,n.jsx)(s.code,{children:"WfSpec"}),"s"]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["For an enterprise-ready distribution of LittleHorse running in your own datacenter, contact ",(0,n.jsx)(s.code,{children:"sales@littlehorse.io"})," to inquire about LittleHorse Platform."]}),"\n",(0,n.jsxs)(s.p,{children:["For a pay-as-you-go, serverless Managed Service of LittleHorse in the cloud, fill out the ",(0,n.jsx)(s.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform",children:"LH Cloud Waitlist Form"}),"."]})]})}function d(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},8453:(e,s,t)=>{t.d(s,{R:()=>l,x:()=>o});var n=t(6540);const r={},i=n.createContext(r);function l(e){const s=n.useContext(i);return n.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:l(e.components),n.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2494aaaa.33797b65.js b/assets/js/2494aaaa.33797b65.js deleted file mode 100644 index c73b3e82d..000000000 --- a/assets/js/2494aaaa.33797b65.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[6958],{1511:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var n=t(4848),s=t(8453);const o={slug:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis"]},r=void 0,a={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.09,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:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"The Challenge of Microservices",permalink:"/blog/challenge-of-microservices"},nextItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"}},l={authorsImageUrls:[void 0]},c=[{value:"What are Microservices?",id:"what-are-microservices",level:2},{value:"Why Now?",id:"why-now",level:2},{value:"Increased Digitization",id:"increased-digitization",level:3},{value:"Cloud Elasticity",id:"cloud-elasticity",level:3},{value:"Why Microservices?",id:"why-microservices",level:2},{value:"Organizational Alignment",id:"organizational-alignment",level:3},{value:"Moving Faster",id:"moving-faster",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const i={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,s.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.p,{children:"If microservices add so much complexity, why bother with the hassle? "}),"\n",(0,n.jsxs)(i.admonition,{type:"info",children:[(0,n.jsx)(i.p,{children:"This is the first part of a 3-part blog series:"}),(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"[This Post]"})," The Promise of Microservices"]}),"\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"/blog/challenge-of-microservices",children:"The Challenge with Microservices"})}),"\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"/blog/microservices-and-workflow",children:"Workflow and Microservices: A Match Made in Heaven"})}),"\n"]})]}),"\n",(0,n.jsxs)(i.p,{children:["We've all ",(0,n.jsx)(i.em,{children:"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",(0,n.jsxs)(i.p,{children:["Microservices have been ",(0,n.jsx)(i.a,{href:"https://www.simform.com/blog/microservices-examples/",children:"deployed widely"})," 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",(0,n.jsx)(i.h2,{id:"what-are-microservices",children:"What are Microservices?"}),"\n",(0,n.jsx)(i.p,{children:'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",(0,n.jsxs)(i.p,{children:["In practice, this means that a user interaction with an application (such as placing an order) might trigger actions that occur in ",(0,n.jsx)(i.em,{children:"many"})," small, independently-deployed software systems, such as:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"A Notification service"}),"\n",(0,n.jsx)(i.li,{children:"An Inventory Management service"}),"\n",(0,n.jsx)(i.li,{children:"A Payments service"}),"\n",(0,n.jsx)(i.li,{children:"An Order History service"}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"From 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",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.img,{alt:"Microservices Architecture",src:t(5375).A+"",width:"861",height:"962"})}),"\n",(0,n.jsxs)(i.p,{children:["In contrast to microservices, a ",(0,n.jsx)(i.em,{children:"monolithic"}),' architecture would serve the entire "place order" request on a single deployable artifact:']}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.img,{alt:"Monolithic Architecture",src:t(153).A+"",width:"250",height:"499"})}),"\n",(0,n.jsx)(i.p,{children:"In 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",(0,n.jsx)(i.p,{children:"Simply 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",(0,n.jsx)(i.h2,{id:"why-now",children:"Why Now?"}),"\n",(0,n.jsx)(i.p,{children:"I 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",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Increased digitization of companies in all business sectors (accelerated by the rise of AI)."}),"\n",(0,n.jsx)(i.li,{children:"Elasticity of cloud computing."}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"increased-digitization",children:"Increased Digitization"}),"\n",(0,n.jsx)(i.p,{children:"The 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",(0,n.jsxs)(i.p,{children:["For example: in the early 2000's, it was perfectly acceptable (even ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:"In 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",(0,n.jsxs)(i.p,{children:["These trends have coincided with (or ",(0,n.jsx)(i.em,{children:"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",(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsx)(i.li,{children:"Allowing large teams of software developers to productively work on an enterprise application in parallel (without stepping on each others' toes)."}),"\n",(0,n.jsx)(i.li,{children:"Ensuring that business requirements are effectively communicated to the entire (larger) software engineering team."}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"cloud-elasticity",children:"Cloud Elasticity"}),"\n",(0,n.jsx)(i.p,{children:"As 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",(0,n.jsxs)(i.p,{children:["The promise of ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:'Beyond 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",(0,n.jsx)(i.h2,{id:"why-microservices",children:"Why Microservices?"}),"\n",(0,n.jsx)(i.p,{children:"Despite 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",(0,n.jsx)(i.h3,{id:"organizational-alignment",children:"Organizational Alignment"}),"\n",(0,n.jsx)(i.p,{children:"As 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",(0,n.jsx)(i.admonition,{type:"note",children:(0,n.jsxs)(i.p,{children:["I am not belittling the engineers of the 90's; the problems they solved were arguably ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:"By breaking applications into smaller services, we can accomplish several important things:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Break up our software engineering team into smaller teams which are each responsible for individual microservices."}),"\n",(0,n.jsx)(i.li,{children:"Allow different components of a system to be developed with separate tech stacks and released independently."}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:'Engineering 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",(0,n.jsxs)(i.p,{children:["As an added benefit, properly-designed microservice architectures can follow the principles of Domain Driven Design. Ideally, a single microservice corresponds to a ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.h3,{id:"moving-faster",children:"Moving Faster"}),"\n",(0,n.jsx)(i.p,{children:"Microservices 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",(0,n.jsxs)(i.p,{children:["These 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 ",(0,n.jsx)(i.code,{children:"Deployment"})," and ",(0,n.jsx)(i.code,{children:"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",(0,n.jsx)(i.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,n.jsxs)(i.p,{children:["The 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 ",(0,n.jsx)(i.em,{children:"necessity"}),". However, those advantages come with a cost."]}),"\n",(0,n.jsxs)(i.p,{children:["We will discuss those challenges in next week's blog post...in the meantime, though, join our ",(0,n.jsx)(i.a,{href:"https://launchpass.com/littlehorsecommunity",children:"Community Slack"})," to get the latest updates!"]})]})}function d(e={}){const{wrapper:i}={...(0,s.R)(),...e.components};return i?(0,n.jsx)(i,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},5375:(e,i,t)=>{t.d(i,{A:()=>n});const n=t.p+"assets/images/2024-08-22-microservices-arch-2389e95f878235ea53f0c0afa2eea967.png"},153:(e,i,t)=>{t.d(i,{A:()=>n});const n=t.p+"assets/images/2024-08-22-monolith-arch-894351f807fd64060d9d05380fc799cf.png"},8453:(e,i,t)=>{t.d(i,{R:()=>r,x:()=>a});var n=t(6540);const s={},o=n.createContext(s);function r(e){const i=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(i):{...i,...e}}),[i,e])}function a(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(o.Provider,{value:i},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2494aaaa.684a0a3d.js b/assets/js/2494aaaa.684a0a3d.js new file mode 100644 index 000000000..e78accbb9 --- /dev/null +++ b/assets/js/2494aaaa.684a0a3d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[6958],{1511:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var n=t(4848),s=t(8453);const o={slug:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},r=void 0,a={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],readingTime:7.09,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:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},unlisted:!1,prevItem:{title:"The Challenge of Microservices",permalink:"/blog/challenge-of-microservices"},nextItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"}},l={authorsImageUrls:[void 0]},c=[{value:"What are Microservices?",id:"what-are-microservices",level:2},{value:"Why Now?",id:"why-now",level:2},{value:"Increased Digitization",id:"increased-digitization",level:3},{value:"Cloud Elasticity",id:"cloud-elasticity",level:3},{value:"Why Microservices?",id:"why-microservices",level:2},{value:"Organizational Alignment",id:"organizational-alignment",level:3},{value:"Moving Faster",id:"moving-faster",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){const i={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,s.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.p,{children:"If microservices add so much complexity, why bother with the hassle? "}),"\n",(0,n.jsxs)(i.admonition,{type:"info",children:[(0,n.jsx)(i.p,{children:"This is the first part of a 3-part blog series:"}),(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"[This Post]"})," The Promise of Microservices"]}),"\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"/blog/challenge-of-microservices",children:"The Challenge with Microservices"})}),"\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"/blog/microservices-and-workflow",children:"Workflow and Microservices: A Match Made in Heaven"})}),"\n"]})]}),"\n",(0,n.jsxs)(i.p,{children:["We've all ",(0,n.jsx)(i.em,{children:"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",(0,n.jsxs)(i.p,{children:["Microservices have been ",(0,n.jsx)(i.a,{href:"https://www.simform.com/blog/microservices-examples/",children:"deployed widely"})," 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",(0,n.jsx)(i.h2,{id:"what-are-microservices",children:"What are Microservices?"}),"\n",(0,n.jsx)(i.p,{children:'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",(0,n.jsxs)(i.p,{children:["In practice, this means that a user interaction with an application (such as placing an order) might trigger actions that occur in ",(0,n.jsx)(i.em,{children:"many"})," small, independently-deployed software systems, such as:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"A Notification service"}),"\n",(0,n.jsx)(i.li,{children:"An Inventory Management service"}),"\n",(0,n.jsx)(i.li,{children:"A Payments service"}),"\n",(0,n.jsx)(i.li,{children:"An Order History service"}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"From 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",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.img,{alt:"Microservices Architecture",src:t(5375).A+"",width:"861",height:"962"})}),"\n",(0,n.jsxs)(i.p,{children:["In contrast to microservices, a ",(0,n.jsx)(i.em,{children:"monolithic"}),' architecture would serve the entire "place order" request on a single deployable artifact:']}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.img,{alt:"Monolithic Architecture",src:t(153).A+"",width:"250",height:"499"})}),"\n",(0,n.jsx)(i.p,{children:"In 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",(0,n.jsx)(i.p,{children:"Simply 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",(0,n.jsx)(i.h2,{id:"why-now",children:"Why Now?"}),"\n",(0,n.jsx)(i.p,{children:"I 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",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Increased digitization of companies in all business sectors (accelerated by the rise of AI)."}),"\n",(0,n.jsx)(i.li,{children:"Elasticity of cloud computing."}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"increased-digitization",children:"Increased Digitization"}),"\n",(0,n.jsx)(i.p,{children:"The 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",(0,n.jsxs)(i.p,{children:["For example: in the early 2000's, it was perfectly acceptable (even ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:"In 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",(0,n.jsxs)(i.p,{children:["These trends have coincided with (or ",(0,n.jsx)(i.em,{children:"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",(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsx)(i.li,{children:"Allowing large teams of software developers to productively work on an enterprise application in parallel (without stepping on each others' toes)."}),"\n",(0,n.jsx)(i.li,{children:"Ensuring that business requirements are effectively communicated to the entire (larger) software engineering team."}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"cloud-elasticity",children:"Cloud Elasticity"}),"\n",(0,n.jsx)(i.p,{children:"As 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",(0,n.jsxs)(i.p,{children:["The promise of ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:'Beyond 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",(0,n.jsx)(i.h2,{id:"why-microservices",children:"Why Microservices?"}),"\n",(0,n.jsx)(i.p,{children:"Despite 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",(0,n.jsx)(i.h3,{id:"organizational-alignment",children:"Organizational Alignment"}),"\n",(0,n.jsx)(i.p,{children:"As 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",(0,n.jsx)(i.admonition,{type:"note",children:(0,n.jsxs)(i.p,{children:["I am not belittling the engineers of the 90's; the problems they solved were arguably ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.p,{children:"By breaking applications into smaller services, we can accomplish several important things:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Break up our software engineering team into smaller teams which are each responsible for individual microservices."}),"\n",(0,n.jsx)(i.li,{children:"Allow different components of a system to be developed with separate tech stacks and released independently."}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:'Engineering 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",(0,n.jsxs)(i.p,{children:["As an added benefit, properly-designed microservice architectures can follow the principles of Domain Driven Design. Ideally, a single microservice corresponds to a ",(0,n.jsx)(i.em,{children:"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",(0,n.jsx)(i.h3,{id:"moving-faster",children:"Moving Faster"}),"\n",(0,n.jsx)(i.p,{children:"Microservices 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",(0,n.jsxs)(i.p,{children:["These 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 ",(0,n.jsx)(i.code,{children:"Deployment"})," and ",(0,n.jsx)(i.code,{children:"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",(0,n.jsx)(i.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,n.jsxs)(i.p,{children:["The 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 ",(0,n.jsx)(i.em,{children:"necessity"}),". However, those advantages come with a cost."]}),"\n",(0,n.jsxs)(i.p,{children:["We will discuss those challenges in next week's blog post...in the meantime, though, join our ",(0,n.jsx)(i.a,{href:"https://launchpass.com/littlehorsecommunity",children:"Community Slack"})," to get the latest updates!"]})]})}function d(e={}){const{wrapper:i}={...(0,s.R)(),...e.components};return i?(0,n.jsx)(i,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},5375:(e,i,t)=>{t.d(i,{A:()=>n});const n=t.p+"assets/images/2024-08-22-microservices-arch-2389e95f878235ea53f0c0afa2eea967.png"},153:(e,i,t)=>{t.d(i,{A:()=>n});const n=t.p+"assets/images/2024-08-22-monolith-arch-894351f807fd64060d9d05380fc799cf.png"},8453:(e,i,t)=>{t.d(i,{R:()=>r,x:()=>a});var n=t(6540);const s={},o=n.createContext(s);function r(e){const i=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(i):{...i,...e}}),[i,e])}function a(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(o.Provider,{value:i},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/32279f3c.8627ffc2.js b/assets/js/32279f3c.8627ffc2.js new file mode 100644 index 000000000..c01763aee --- /dev/null +++ b/assets/js/32279f3c.8627ffc2.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:()=>l,contentTitle:()=>s,default:()=>p,frontMatter:()=>r,metadata:()=>i,toc:()=>c});var n=a(4848),o=a(8453);const r={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},s="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."},{inline:!1,label:"Integration Patterns",permalink:"/blog/tags/integration-patterns/",description:"A 5-part blog series on Integration Patterns that are useful for event-driven systems."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:6.235,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","integration-patterns","littlehorse"]},unlisted:!1,prevItem:{title:"Integration Patterns: Transactional Outbox",permalink:"/blog/transactional-outbox"},nextItem:{title:"The Basics of Workflow",permalink:"/blog/basics-of-workflow"}},l={authorsImageUrls:[void 0]},c=[];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 p(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:()=>s,x:()=>i});var n=a(6540);const o={},r=n.createContext(o);function s(t){const e=n.useContext(r);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:s(t.components),n.createElement(r.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 deleted file mode 100644 index ea8e833aa..000000000 --- a/assets/js/32279f3c.96e1a56d.js +++ /dev/null @@ -1 +0,0 @@ -"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/3a2db09e.29ad30f3.js b/assets/js/3a2db09e.29ad30f3.js new file mode 100644 index 000000000..a9579a265 --- /dev/null +++ b/assets/js/3a2db09e.29ad30f3.js @@ -0,0 +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":6},{"label":"Integration Patterns","permalink":"/blog/tags/integration-patterns/","description":"A 5-part blog series on Integration Patterns that are useful for event-driven systems.","count":2},{"label":"LittleHorse Orchestrator","permalink":"/blog/tags/littlehorse/","description":"Information about the LittleHorse Orchestrator.","count":3},{"label":"Microservices and Workflow","permalink":"/blog/tags/microservice-and-workflow/","description":"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties.","count":3},{"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/3a2db09e.c5741c99.js b/assets/js/3a2db09e.c5741c99.js deleted file mode 100644 index 691d408aa..000000000 --- a/assets/js/3a2db09e.c5741c99.js +++ /dev/null @@ -1 +0,0 @@ -"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/4781d3d2.d571214a.js b/assets/js/4781d3d2.d571214a.js deleted file mode 100644 index ff918efed..000000000 --- a/assets/js/4781d3d2.d571214a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8892],{8969:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>a,contentTitle:()=>r,default:()=>m,frontMatter:()=>n,metadata:()=>c,toc:()=>l});var s=o(4848),i=o(8453);const n={slug:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis"]},r=void 0,c={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:7.09,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:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"The Challenge of Microservices",permalink:"/blog/challenge-of-microservices"},nextItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"}},a={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,i.R)(),...e.components};return(0,s.jsx)(t.p,{children:"If microservices add so much complexity, why bother with the hassle?"})}function m(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,o)=>{o.d(t,{R:()=>r,x:()=>c});var s=o(6540);const i={},n=s.createContext(i);function r(e){const t=s.useContext(n);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),s.createElement(n.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4781d3d2.e1d5b690.js b/assets/js/4781d3d2.e1d5b690.js new file mode 100644 index 000000000..4eeed9976 --- /dev/null +++ b/assets/js/4781d3d2.e1d5b690.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8892],{8969:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>a,contentTitle:()=>n,default:()=>m,frontMatter:()=>r,metadata:()=>c,toc:()=>l});var i=o(4848),s=o(8453);const r={slug:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},n=void 0,c={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],readingTime:7.09,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:"promise-of-microservices",title:"The Promise of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},unlisted:!1,prevItem:{title:"The Challenge of Microservices",permalink:"/blog/challenge-of-microservices"},nextItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"}},a={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,s.R)(),...e.components};return(0,i.jsx)(t.p,{children:"If microservices add so much complexity, why bother with the hassle?"})}function m(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>n,x:()=>c});var i=o(6540);const s={},r=i.createContext(s);function n(e){const t=i.useContext(r);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:n(e.components),i.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/57cb429f.2b0514fd.js b/assets/js/57cb429f.2b0514fd.js new file mode 100644 index 000000000..8c66623d6 --- /dev/null +++ b/assets/js/57cb429f.2b0514fd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[2775],{2879:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var s=o(4848),n=o(8453);const r={title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.5.0-release",source:"@site/blog/2023-09-08-0.5.0-release.md",title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",date:"2023-09-08T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:5.205,hasTruncateMarker:!0,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.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"},nextItem:{title:"Releasing 0.2.0",permalink:"/blog/littlehorse-0.2.0-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,n.R)(),...e.components};return(0,s.jsxs)(t.p,{children:["We are excited to announce the minor release ",(0,s.jsx)(t.code,{children:"0.5.0"}),"."]})}function u(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>i,x:()=>a});var s=o(6540);const n={},r=s.createContext(n);function i(e){const t=s.useContext(r);return s.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:i(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/57cb429f.4258d9f0.js b/assets/js/57cb429f.4258d9f0.js deleted file mode 100644 index 24f8d171d..000000000 --- a/assets/js/57cb429f.4258d9f0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[2775],{2879:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>a,contentTitle:()=>i,default:()=>u,frontMatter:()=>n,metadata:()=>l,toc:()=>c});var s=o(4848),r=o(8453);const n={title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,l={permalink:"/blog/littlehorse-0.5.0-release",source:"@site/blog/2023-09-08-0.5.0-release.md",title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",date:"2023-09-08T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:5.205,hasTruncateMarker:!0,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.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"},nextItem:{title:"Releasing 0.2.0",permalink:"/blog/littlehorse-0.2.0-release"}},a={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,s.jsxs)(t.p,{children:["We are excited to announce the minor release ",(0,s.jsx)(t.code,{children:"0.5.0"}),"."]})}function u(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>i,x:()=>l});var s=o(6540);const r={},n=s.createContext(r);function i(e){const t=s.useContext(n);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(n.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5a5f8fd5.47e8017b.js b/assets/js/5a5f8fd5.bd685f79.js similarity index 76% rename from assets/js/5a5f8fd5.47e8017b.js rename to assets/js/5a5f8fd5.bd685f79.js index 0274a732e..0db7ac1ee 100644 --- a/assets/js/5a5f8fd5.47e8017b.js +++ b/assets/js/5a5f8fd5.bd685f79.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":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 +"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":3,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/littlehorse/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":3,"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/5af1e9f6.578d9587.js b/assets/js/5af1e9f6.578d9587.js new file mode 100644 index 000000000..f3a5b5fdd --- /dev/null +++ b/assets/js/5af1e9f6.578d9587.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8648],{9451:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>o,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var s=t(4848),n=t(8453);const r={title:"Releasing 0.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},l=void 0,a={permalink:"/blog/littlehorse-0.8-release",source:"@site/blog/2024-03-26-0.8.1-release.md",title:"Releasing 0.8",description:"Hardening Security",date:"2024-03-26T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.955,hasTruncateMarker:!0,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.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"},nextItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"}},o={authorsImageUrls:[void 0]},c=[{value:"New Features",id:"new-features",level:2},{value:"Dynamic Task Execution",id:"dynamic-task-execution",level:3},{value:"Per-Thread Failure Handlers",id:"per-thread-failure-handlers",level:3},{value:"Content inEXCEPTION
s",id:"content-in-exceptions",level:3},{value:"Multi-Tenancy Improvements",id:"multi-tenancy-improvements",level:3},{value:"Kafka Security Protocol Support",id:"kafka-security-protocol-support",level:3},{value:"LittleHorse Canary",id:"littlehorse-canary",level:3},{value:"Exponential Backoff Retry Policy",id:"exponential-backoff-retry-policy",level:3},{value:"JavaScript Client",id:"javascript-client",level:3},{value:"Bugfixes",id:"bugfixes",level:3},{value:"Looking Forward",id:"looking-forward",level:2}];function h(e){const i={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",ul:"ul",...(0,n.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(i.p,{children:["The ",(0,s.jsx)(i.code,{children:"0.8"})," release of LittleHorse is out! This pre-1.0 release contains many new features, security enhancements, and performance improvements."]}),"\n",(0,s.jsx)(i.h2,{id:"new-features",children:"New Features"}),"\n",(0,s.jsx)(i.p,{children:"New features in this release cover some edge-cases in workflow development which came up from some initial pilots and internal usage of the platform."}),"\n",(0,s.jsx)(i.h3,{id:"dynamic-task-execution",children:"Dynamic Task Execution"}),"\n",(0,s.jsxs)(i.p,{children:["Before this release, a ",(0,s.jsx)(i.code,{children:"TaskNode"})," had a hard-coded reference to a ",(0,s.jsx)(i.code,{children:"TaskDef"}),". This means that every single ",(0,s.jsx)(i.code,{children:"WfRun"})," that reaches the same ",(0,s.jsx)(i.code,{children:"Node"})," in a ",(0,s.jsx)(i.code,{children:"WfSpec"})," ends up executing the same ",(0,s.jsx)(i.code,{children:"TaskDef"}),"."]}),"\n",(0,s.jsxs)(i.p,{children:["However, in LittleHorse Enterprises LLC's upcoming Control Plane project (a system for dynamically provisioning LittleHorse clusters as a SaaS service), we anticipate a special use-case (which we will blog about this upcoming fall) wherein we need to ",(0,s.jsx)(i.em,{children:"choose"})," which ",(0,s.jsx)(i.code,{children:"TaskDef"})," is executed dynamically at runtime."]}),"\n",(0,s.jsxs)(i.p,{children:["Specifically, depending on an input variable to a ",(0,s.jsx)(i.code,{children:"WfRun"})," (in this case, the ",(0,s.jsx)(i.code,{children:"data-plane-id"})," variable), we need to execute a different ",(0,s.jsx)(i.code,{children:"TaskDef"})," so that the ",(0,s.jsx)(i.code,{children:"TaskRun"})," is executed by a speciific Task Worker in a specific location. We will blog about that use-case later."]}),"\n",(0,s.jsx)(i.h3,{id:"per-thread-failure-handlers",children:"Per-Thread Failure Handlers"}),"\n",(0,s.jsxs)(i.p,{children:["Since the ",(0,s.jsx)(i.code,{children:"0.1.0"})," release of LittleHorse it has been possible to put a ",(0,s.jsx)(i.code,{children:"FailureHandler"})," on any ",(0,s.jsx)(i.code,{children:"Node"}),", such that if the ",(0,s.jsx)(i.code,{children:"NodeRun"})," fails, then a Failure Handler thread is"]}),"\n",(0,s.jsxs)(i.h3,{id:"content-in-exceptions",children:["Content in ",(0,s.jsx)(i.code,{children:"EXCEPTION"}),"s"]}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"#714"}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"multi-tenancy-improvements",children:"Multi-Tenancy Improvements"}),"\n",(0,s.jsxs)(i.p,{children:["Multi-Tenancy has been quietly under development in the LittleHorse Server since the ",(0,s.jsx)(i.code,{children:"0.6.0"})," release introduced a breaking change to allow for it last October. The ",(0,s.jsx)(i.code,{children:"0.8"})," release continues to progress towards making Multi-Tenancy generally-available."]}),"\n",(0,s.jsx)(i.p,{children:"This release includes two new major features for Multi-Tenancy:"}),"\n",(0,s.jsxs)(i.ol,{children:["\n",(0,s.jsxs)(i.li,{children:["Allowing Python and Go clients to set the ",(0,s.jsx)(i.code,{children:"tenant-id"})," header using ",(0,s.jsx)(i.code,{children:"LHC_TENANT_id"})," (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/704",children:"#704"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Allowing administrative ",(0,s.jsx)(i.code,{children:"Principal"}),"s with admin privileges over multiple ",(0,s.jsx)(i.code,{children:"Tenant"}),"s: (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/679",children:"#679"}),")"]}),"\n"]}),"\n",(0,s.jsxs)(i.p,{children:["Multi-Tenancy and support for authentication + fine-grained ACL's via ",(0,s.jsx)(i.code,{children:"Principal"}),"s has been a labor of love implemented by ",(0,s.jsx)(i.a,{href:"https://github.com/eduwercamacaro",children:"Eduwer Camacaro"}),", who has grown into the role of Grumpy Maintainer of LittleHorse."]}),"\n",(0,s.jsx)(i.h3,{id:"kafka-security-protocol-support",children:"Kafka Security Protocol Support"}),"\n",(0,s.jsxs)(i.p,{children:["Prior to release ",(0,s.jsx)(i.code,{children:"0.8"}),", the LH Server could only access a Kafka cluster with either:"]}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Plaintext access with no security."}),"\n",(0,s.jsx)(i.li,{children:"TLS with no authentication."}),"\n",(0,s.jsx)(i.li,{children:"MTLS security."}),"\n"]}),"\n",(0,s.jsxs)(i.p,{children:["PR ",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/716",children:"#716"})," introduced the following Server configurations:"]}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:(0,s.jsx)(i.code,{children:"LHS_KAFKA_SECURITY_PROTOCOL"})}),"\n",(0,s.jsx)(i.li,{children:(0,s.jsx)(i.code,{children:"LHS_KAFKA_SASL_MECHANISM"})}),"\n",(0,s.jsx)(i.li,{children:(0,s.jsx)(i.code,{children:"LHS_KAFKA_SASL_JAAS_CONFIG"})}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"This allows for access to any Kafka cluster except those requiring loading custom implementations of callbacks on the client side (for example, using the Strimzi OAuth Plug-in)."}),"\n",(0,s.jsx)(i.p,{children:"It is now possible to run LH with Kafka as:"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"No security (PLAINTEXT)"}),"\n",(0,s.jsx)(i.li,{children:"TLS on the brokers, no authentication (SSL)"}),"\n",(0,s.jsx)(i.li,{children:"MTLS on the brokers (SSL with TRUSTSTORE set)"}),"\n",(0,s.jsx)(i.li,{children:"SASL with any JAAS config (SASL_SSL)"}),"\n",(0,s.jsx)(i.li,{children:"Confluent Cloud."}),"\n"]}),"\n",(0,s.jsx)(i.h3,{id:"littlehorse-canary",children:"LittleHorse Canary"}),"\n",(0,s.jsxs)(i.p,{children:["The LittleHorse Canary was released in early access. Inspired by the ",(0,s.jsx)(i.a,{href:"https://strimzi.io/blog/2021/11/09/canary/",children:"Strimzi Canary"})," for Apache Kafka, the LittleHorse Canary is a system that runs workflows on LittleHorse and reports on the health of the cluster(s) that it is monitoring."]}),"\n",(0,s.jsx)(i.p,{children:"The LH Canary system comprises two components:"}),"\n",(0,s.jsxs)(i.ol,{children:["\n",(0,s.jsx)(i.li,{children:"The Metronome, which runs workflows and sends metric beats to a Kafka topic."}),"\n",(0,s.jsx)(i.li,{children:"The Aggregator, which consumes the metrics beats Kafka topic and aggregates metrics to be exposed to Prometheus and a GRPC API."}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"The goal of the Canary is to monitor, profile, and benchmark LittleHorse Clusters from the same exact perspective as the clients who use them."}),"\n",(0,s.jsxs)(i.p,{children:["The Canary is the brain child of ",(0,s.jsx)(i.a,{href:"https://github.com/sauljabin",children:"Sa\xfal Pi\xf1a"}),", who is also the author of the popular ",(0,s.jsx)(i.a,{href:"https://github.com/sauljabin/kaskade",children:"Kaskade"})," TUI for Apache Kafka."]}),"\n",(0,s.jsx)(i.h3,{id:"exponential-backoff-retry-policy",children:"Exponential Backoff Retry Policy"}),"\n",(0,s.jsxs)(i.p,{children:["PR (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/707",children:"#707"}),") introduced the ability to configure exponential backoff for ",(0,s.jsx)(i.code,{children:"TaskRun"})," retries. Previously, only immediate retries were supported."]}),"\n",(0,s.jsx)(i.h3,{id:"javascript-client",children:"JavaScript Client"}),"\n",(0,s.jsxs)(i.p,{children:["We published the first version of ",(0,s.jsx)(i.code,{children:"littlehorse-client"})," on NPM ",(0,s.jsx)(i.a,{href:"https://www.npmjs.com/package/littlehorse-client",children:"here"}),". This client contains the ",(0,s.jsx)(i.code,{children:"LHConfig"})," in javascript, which provides access to our LittleHorse GRPC API. Note that we do not yet support a JavaScript Task Worker nor a JavaScript ",(0,s.jsx)(i.code,{children:"WfSpec"})," SDK."]}),"\n",(0,s.jsx)(i.h3,{id:"bugfixes",children:"Bugfixes"}),"\n",(0,s.jsx)(i.p,{children:"In this release, we fixed several bugs:"}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:["Task Worker improperly reported ",(0,s.jsx)(i.code,{children:"EXCEPTION"}),"s and ",(0,s.jsx)(i.code,{children:"ERROR"}),"s when throwing ",(0,s.jsx)(i.code,{children:"LHTaskException"})," (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/pull/738",children:"#738"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes task queue rehydration (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/pull/727",children:"#727"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes the Retention Policy for ",(0,s.jsx)(i.code,{children:"ExternalEventDef"}),"'s (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/724",children:"#724"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes deadlock in Java task worker (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/723",children:"#723"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes concurrency bug with the ",(0,s.jsx)(i.code,{children:"AsyncWaiter"})," in the server (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/719",children:"#719"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes various issues from soak tests (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/706",children:"#706"}),")"]}),"\n",(0,s.jsxs)(i.li,{children:["Fixes to ",(0,s.jsx)(i.code,{children:"NodeRun"})," lifecycle (",(0,s.jsx)(i.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/665",children:"#665"}),")."]}),"\n"]}),"\n",(0,s.jsx)(i.h2,{id:"looking-forward",children:"Looking Forward"}),"\n",(0,s.jsx)(i.p,{children:"We continue to stabilize our API and add features that cover edge cases. Load testing, chaos testing, and soak testing are an ongoing project, and we are working with the Apache Kafka Community on a few bugfixes in the Kafka Streams library which is heavily used in the core of LittleHorse."}),"\n",(0,s.jsxs)(i.p,{children:["Once those action items are resolved, we will make a ",(0,s.jsx)(i.code,{children:"1.0"})," release candidate. However, in the meantime we don't expect any massively-breaking API changes at the protocol level. However, certain syntactical changes may occur in our SDK's (especially Go and Python)."]})]})}function d(e={}){const{wrapper:i}={...(0,n.R)(),...e.components};return i?(0,s.jsx)(i,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,i,t)=>{t.d(i,{R:()=>l,x:()=>a});var s=t(6540);const n={},r=s.createContext(n);function l(e){const i=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(i):{...i,...e}}),[i,e])}function a(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:l(e.components),s.createElement(r.Provider,{value:i},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/5af1e9f6.bc46dcc3.js b/assets/js/5af1e9f6.bc46dcc3.js
deleted file mode 100644
index fcfa50e92..000000000
--- a/assets/js/5af1e9f6.bc46dcc3.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[8648],{9451:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>a,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>o,toc:()=>c});var s=i(4848),n=i(8453);const r={title:"Releasing 0.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},l=void 0,o={permalink:"/blog/littlehorse-0.8-release",source:"@site/blog/2024-03-26-0.8.1-release.md",title:"Releasing 0.8",description:"Hardening Security",date:"2024-03-26T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.955,hasTruncateMarker:!0,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.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"},nextItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"}},a={authorsImageUrls:[void 0]},c=[{value:"New Features",id:"new-features",level:2},{value:"Dynamic Task Execution",id:"dynamic-task-execution",level:3},{value:"Per-Thread Failure Handlers",id:"per-thread-failure-handlers",level:3},{value:"Content in EXCEPTION
s",id:"content-in-exceptions",level:3},{value:"Multi-Tenancy Improvements",id:"multi-tenancy-improvements",level:3},{value:"Kafka Security Protocol Support",id:"kafka-security-protocol-support",level:3},{value:"LittleHorse Canary",id:"littlehorse-canary",level:3},{value:"Exponential Backoff Retry Policy",id:"exponential-backoff-retry-policy",level:3},{value:"JavaScript Client",id:"javascript-client",level:3},{value:"Bugfixes",id:"bugfixes",level:3},{value:"Looking Forward",id:"looking-forward",level:2}];function h(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",ul:"ul",...(0,n.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"0.8"})," release of LittleHorse is out! This pre-1.0 release contains many new features, security enhancements, and performance improvements."]}),"\n",(0,s.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,s.jsx)(t.p,{children:"New features in this release cover some edge-cases in workflow development which came up from some initial pilots and internal usage of the platform."}),"\n",(0,s.jsx)(t.h3,{id:"dynamic-task-execution",children:"Dynamic Task Execution"}),"\n",(0,s.jsxs)(t.p,{children:["Before this release, a ",(0,s.jsx)(t.code,{children:"TaskNode"})," had a hard-coded reference to a ",(0,s.jsx)(t.code,{children:"TaskDef"}),". This means that every single ",(0,s.jsx)(t.code,{children:"WfRun"})," that reaches the same ",(0,s.jsx)(t.code,{children:"Node"})," in a ",(0,s.jsx)(t.code,{children:"WfSpec"})," ends up executing the same ",(0,s.jsx)(t.code,{children:"TaskDef"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["However, in LittleHorse Enterprises LLC's upcoming Control Plane project (a system for dynamically provisioning LittleHorse clusters as a SaaS service), we anticipate a special use-case (which we will blog about this upcoming fall) wherein we need to ",(0,s.jsx)(t.em,{children:"choose"})," which ",(0,s.jsx)(t.code,{children:"TaskDef"})," is executed dynamically at runtime."]}),"\n",(0,s.jsxs)(t.p,{children:["Specifically, depending on an input variable to a ",(0,s.jsx)(t.code,{children:"WfRun"})," (in this case, the ",(0,s.jsx)(t.code,{children:"data-plane-id"})," variable), we need to execute a different ",(0,s.jsx)(t.code,{children:"TaskDef"})," so that the ",(0,s.jsx)(t.code,{children:"TaskRun"})," is executed by a speciific Task Worker in a specific location. We will blog about that use-case later."]}),"\n",(0,s.jsx)(t.h3,{id:"per-thread-failure-handlers",children:"Per-Thread Failure Handlers"}),"\n",(0,s.jsxs)(t.p,{children:["Since the ",(0,s.jsx)(t.code,{children:"0.1.0"})," release of LittleHorse it has been possible to put a ",(0,s.jsx)(t.code,{children:"FailureHandler"})," on any ",(0,s.jsx)(t.code,{children:"Node"}),", such that if the ",(0,s.jsx)(t.code,{children:"NodeRun"})," fails, then a Failure Handler thread is"]}),"\n",(0,s.jsxs)(t.h3,{id:"content-in-exceptions",children:["Content in ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),"s"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"#714"}),"\n"]}),"\n",(0,s.jsx)(t.h3,{id:"multi-tenancy-improvements",children:"Multi-Tenancy Improvements"}),"\n",(0,s.jsxs)(t.p,{children:["Multi-Tenancy has been quietly under development in the LittleHorse Server since the ",(0,s.jsx)(t.code,{children:"0.6.0"})," release introduced a breaking change to allow for it last October. The ",(0,s.jsx)(t.code,{children:"0.8"})," release continues to progress towards making Multi-Tenancy generally-available."]}),"\n",(0,s.jsx)(t.p,{children:"This release includes two new major features for Multi-Tenancy:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["Allowing Python and Go clients to set the ",(0,s.jsx)(t.code,{children:"tenant-id"})," header using ",(0,s.jsx)(t.code,{children:"LHC_TENANT_id"})," (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/704",children:"#704"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Allowing administrative ",(0,s.jsx)(t.code,{children:"Principal"}),"s with admin privileges over multiple ",(0,s.jsx)(t.code,{children:"Tenant"}),"s: (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/679",children:"#679"}),")"]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Multi-Tenancy and support for authentication + fine-grained ACL's via ",(0,s.jsx)(t.code,{children:"Principal"}),"s has been a labor of love implemented by ",(0,s.jsx)(t.a,{href:"https://github.com/eduwercamacaro",children:"Eduwer Camacaro"}),", who has grown into the role of Grumpy Maintainer of LittleHorse."]}),"\n",(0,s.jsx)(t.h3,{id:"kafka-security-protocol-support",children:"Kafka Security Protocol Support"}),"\n",(0,s.jsxs)(t.p,{children:["Prior to release ",(0,s.jsx)(t.code,{children:"0.8"}),", the LH Server could only access a Kafka cluster with either:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Plaintext access with no security."}),"\n",(0,s.jsx)(t.li,{children:"TLS with no authentication."}),"\n",(0,s.jsx)(t.li,{children:"MTLS security."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["PR ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/716",children:"#716"})," introduced the following Server configurations:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:(0,s.jsx)(t.code,{children:"LHS_KAFKA_SECURITY_PROTOCOL"})}),"\n",(0,s.jsx)(t.li,{children:(0,s.jsx)(t.code,{children:"LHS_KAFKA_SASL_MECHANISM"})}),"\n",(0,s.jsx)(t.li,{children:(0,s.jsx)(t.code,{children:"LHS_KAFKA_SASL_JAAS_CONFIG"})}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"This allows for access to any Kafka cluster except those requiring loading custom implementations of callbacks on the client side (for example, using the Strimzi OAuth Plug-in)."}),"\n",(0,s.jsx)(t.p,{children:"It is now possible to run LH with Kafka as:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"No security (PLAINTEXT)"}),"\n",(0,s.jsx)(t.li,{children:"TLS on the brokers, no authentication (SSL)"}),"\n",(0,s.jsx)(t.li,{children:"MTLS on the brokers (SSL with TRUSTSTORE set)"}),"\n",(0,s.jsx)(t.li,{children:"SASL with any JAAS config (SASL_SSL)"}),"\n",(0,s.jsx)(t.li,{children:"Confluent Cloud."}),"\n"]}),"\n",(0,s.jsx)(t.h3,{id:"littlehorse-canary",children:"LittleHorse Canary"}),"\n",(0,s.jsxs)(t.p,{children:["The LittleHorse Canary was released in early access. Inspired by the ",(0,s.jsx)(t.a,{href:"https://strimzi.io/blog/2021/11/09/canary/",children:"Strimzi Canary"})," for Apache Kafka, the LittleHorse Canary is a system that runs workflows on LittleHorse and reports on the health of the cluster(s) that it is monitoring."]}),"\n",(0,s.jsx)(t.p,{children:"The LH Canary system comprises two components:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"The Metronome, which runs workflows and sends metric beats to a Kafka topic."}),"\n",(0,s.jsx)(t.li,{children:"The Aggregator, which consumes the metrics beats Kafka topic and aggregates metrics to be exposed to Prometheus and a GRPC API."}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"The goal of the Canary is to monitor, profile, and benchmark LittleHorse Clusters from the same exact perspective as the clients who use them."}),"\n",(0,s.jsxs)(t.p,{children:["The Canary is the brain child of ",(0,s.jsx)(t.a,{href:"https://github.com/sauljabin",children:"Sa\xfal Pi\xf1a"}),", who is also the author of the popular ",(0,s.jsx)(t.a,{href:"https://github.com/sauljabin/kaskade",children:"Kaskade"})," TUI for Apache Kafka."]}),"\n",(0,s.jsx)(t.h3,{id:"exponential-backoff-retry-policy",children:"Exponential Backoff Retry Policy"}),"\n",(0,s.jsxs)(t.p,{children:["PR (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/707",children:"#707"}),") introduced the ability to configure exponential backoff for ",(0,s.jsx)(t.code,{children:"TaskRun"})," retries. Previously, only immediate retries were supported."]}),"\n",(0,s.jsx)(t.h3,{id:"javascript-client",children:"JavaScript Client"}),"\n",(0,s.jsxs)(t.p,{children:["We published the first version of ",(0,s.jsx)(t.code,{children:"littlehorse-client"})," on NPM ",(0,s.jsx)(t.a,{href:"https://www.npmjs.com/package/littlehorse-client",children:"here"}),". This client contains the ",(0,s.jsx)(t.code,{children:"LHConfig"})," in javascript, which provides access to our LittleHorse GRPC API. Note that we do not yet support a JavaScript Task Worker nor a JavaScript ",(0,s.jsx)(t.code,{children:"WfSpec"})," SDK."]}),"\n",(0,s.jsx)(t.h3,{id:"bugfixes",children:"Bugfixes"}),"\n",(0,s.jsx)(t.p,{children:"In this release, we fixed several bugs:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Task Worker improperly reported ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),"s and ",(0,s.jsx)(t.code,{children:"ERROR"}),"s when throwing ",(0,s.jsx)(t.code,{children:"LHTaskException"})," (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/pull/738",children:"#738"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes task queue rehydration (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/pull/727",children:"#727"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes the Retention Policy for ",(0,s.jsx)(t.code,{children:"ExternalEventDef"}),"'s (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/724",children:"#724"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes deadlock in Java task worker (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/723",children:"#723"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes concurrency bug with the ",(0,s.jsx)(t.code,{children:"AsyncWaiter"})," in the server (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/719",children:"#719"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes various issues from soak tests (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/706",children:"#706"}),")"]}),"\n",(0,s.jsxs)(t.li,{children:["Fixes to ",(0,s.jsx)(t.code,{children:"NodeRun"})," lifecycle (",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/665",children:"#665"}),")."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"looking-forward",children:"Looking Forward"}),"\n",(0,s.jsx)(t.p,{children:"We continue to stabilize our API and add features that cover edge cases. Load testing, chaos testing, and soak testing are an ongoing project, and we are working with the Apache Kafka Community on a few bugfixes in the Kafka Streams library which is heavily used in the core of LittleHorse."}),"\n",(0,s.jsxs)(t.p,{children:["Once those action items are resolved, we will make a ",(0,s.jsx)(t.code,{children:"1.0"})," release candidate. However, in the meantime we don't expect any massively-breaking API changes at the protocol level. However, certain syntactical changes may occur in our SDK's (especially Go and Python)."]})]})}function d(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,i)=>{i.d(t,{R:()=>l,x:()=>o});var s=i(6540);const n={},r=s.createContext(n);function l(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:l(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/5b3e9818.5bdb5f9d.js b/assets/js/5b3e9818.5bdb5f9d.js
new file mode 100644
index 000000000..52a4d71a2
--- /dev/null
+++ b/assets/js/5b3e9818.5bdb5f9d.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[538],{5002:t=>{t.exports=JSON.parse('{"tag":{"label":"Integration Patterns","permalink":"/blog/tags/integration-patterns/","description":"A 5-part blog series on Integration Patterns that are useful for event-driven systems.","allTagsPath":"/blog/tags","count":2,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/integration-patterns/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":2,"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/5b77cc54.17f618c6.js b/assets/js/5b77cc54.17f618c6.js
new file mode 100644
index 000000000..d19f25ac4
--- /dev/null
+++ b/assets/js/5b77cc54.17f618c6.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[3457],{517:e=>{e.exports=JSON.parse('{"tag":{"label":"Microservices and Workflow","permalink":"/blog/tags/microservice-and-workflow/","description":"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties.","allTagsPath":"/blog/tags","count":3,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/microservice-and-workflow/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":3,"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/7f375326.7d79d881.js b/assets/js/7f375326.7d79d881.js
new file mode 100644
index 000000000..cd2e4dcd5
--- /dev/null
+++ b/assets/js/7f375326.7d79d881.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1886],{1777:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>i,toc:()=>c});var o=s(4848),n=s(8453);const r={title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},a=void 0,i={permalink:"/blog/littlehorse-0.7-release",source:"@site/blog/2024-01-28-0.7-release.md",title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",date:"2024-01-28T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:4.535,hasTruncateMarker:!0,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.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"},nextItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,n.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,o.jsx)(t.code,{children:"0.7.2"}),"!"]})}function u(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:()=>a,x:()=>i});var o=s(6540);const n={},r=o.createContext(n);function a(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(n):e.components||n:a(e.components),o.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/7f375326.fc1f2514.js b/assets/js/7f375326.fc1f2514.js
deleted file mode 100644
index 25805d18e..000000000
--- a/assets/js/7f375326.fc1f2514.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1886],{1777:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>n,metadata:()=>a,toc:()=>c});var o=s(4848),r=s(8453);const n={title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.7-release",source:"@site/blog/2024-01-28-0.7-release.md",title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",date:"2024-01-28T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:4.535,hasTruncateMarker:!0,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.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"},nextItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,o.jsx)(t.code,{children:"0.7.2"}),"!"]})}function u(e={}){const{wrapper:t}={...(0,r.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:()=>i,x:()=>a});var o=s(6540);const r={},n=o.createContext(r);function i(e){const t=o.useContext(n);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(r):e.components||r:i(e.components),o.createElement(n.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/7f97f38f.38383c7b.js b/assets/js/7f97f38f.38383c7b.js
new file mode 100644
index 000000000..4665e7a4b
--- /dev/null
+++ b/assets/js/7f97f38f.38383c7b.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[5739],{7027:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>m,frontMatter:()=>s,metadata:()=>a,toc:()=>l});var n=o(4848),i=o(8453);const s={slug:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},r=void 0,a={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],readingTime:8.93,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:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},unlisted:!1,prevItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"},nextItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"}},c={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,i.R)(),...e.components};return(0,n.jsx)(t.p,{children:"Microservices are often necessary, but unfortunately they bring with them some baggage."})}function m(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>r,x:()=>a});var n=o(6540);const i={},s=n.createContext(i);function r(e){const t=n.useContext(s);return n.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(i):e.components||i:r(e.components),n.createElement(s.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/7f97f38f.38b41150.js b/assets/js/7f97f38f.38b41150.js
deleted file mode 100644
index d00db35b1..000000000
--- a/assets/js/7f97f38f.38b41150.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[5739],{7027:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>i,toc:()=>l});var o=n(4848),s=n(8453);const a={slug:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis"]},r=void 0,i={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:!1,label:"Technical Analysis",permalink:"/blog/tags/analysis/",description:"Analysis of the current and future state of Technical Architecture."}],readingTime:8.93,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:"challenge-of-microservices",title:"The Challenge of Microservices",authors:["coltmcnealy"],tags:["analysis"]},unlisted:!1,prevItem:{title:"Releasing 0.11",permalink:"/blog/littlehorse-0.11-release"},nextItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"}},c={authorsImageUrls:[void 0]},l=[];function h(e){const t={p:"p",...(0,s.R)(),...e.components};return(0,o.jsx)(t.p,{children:"Microservices are often necessary, but unfortunately they bring with them some baggage."})}function u(e={}){const{wrapper:t}={...(0,s.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:()=>r,x:()=>i});var o=n(6540);const s={},a=o.createContext(s);function r(e){const t=o.useContext(a);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(s):e.components||s:r(e.components),o.createElement(a.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/814f3328.9db599c7.js b/assets/js/814f3328.9db599c7.js
deleted file mode 100644
index 57137dcc4..000000000
--- a/assets/js/814f3328.9db599c7.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":"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/814f3328.a5db8ea4.js b/assets/js/814f3328.a5db8ea4.js
new file mode 100644
index 000000000..195a8518a
--- /dev/null
+++ b/assets/js/814f3328.a5db8ea4.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: Transactional Outbox","permalink":"/blog/transactional-outbox","unlisted":false,"date":"2024-09-30T00:00:00.000Z"},{"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/81a46808.1d30f3e3.js b/assets/js/81a46808.1d30f3e3.js
new file mode 100644
index 000000000..f35b36174
--- /dev/null
+++ b/assets/js/81a46808.1d30f3e3.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[1715],{6112:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>d,frontMatter:()=>n,metadata:()=>a,toc:()=>c});var i=s(4848),r=s(8453);const n={title:"Releasing 0.10",description:"Releasing LittleHorse `0.10`",slug:"littlehorse-0.10-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},l=void 0,a={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:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.005,hasTruncateMarker:!0,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:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"The Promise of Microservices",permalink:"/blog/promise-of-microservices"},nextItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"}},o={authorsImageUrls:[void 0]},c=[{value:"New Features",id:"new-features",level:2},{value:"lhctl
Binaries and Release Notes",id:"lhctl-binaries-and-release-notes",level:3},{value:"Reliability during Rebalances",id:"reliability-during-rebalances",level:3},{value:"Rescue Failed Workflows",id:"rescue-failed-workflows",level:3},{value:"mTLS Principals",id:"mtls-principals",level:3},{value:"Dashboard Enhancements",id:"dashboard-enhancements",level:3},{value:"What's Next?",id:"whats-next",level:2}];function h(e){const t={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:"0.10"})," release brings with it significant performance and reliability improvements. "]}),"\n",(0,i.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,i.jsxs)(t.h3,{id:"lhctl-binaries-and-release-notes",children:[(0,i.jsx)(t.code,{children:"lhctl"})," Binaries and Release Notes"]}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:"0.10.0"})," release comes with a new ",(0,i.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases",children:"Release Page"}),", including ",(0,i.jsx)(t.code,{children:"lhctl"})," binaries built for ARM, Intel, and Windows."]}),"\n",(0,i.jsx)(t.h3,{id:"reliability-during-rebalances",children:"Reliability during Rebalances"}),"\n",(0,i.jsxs)(t.p,{children:["PR ",(0,i.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/872",children:"#872"})," improves the reliability of LittleHorse during Kafka Streams rebalances. Previously, if a write request (eg. ",(0,i.jsx)(t.code,{children:"rpc RunWf"}),') was received just before a rebalance, certain requests would "time out" from the client perspective and return a ',(0,i.jsx)(t.code,{children:"DEADLINE_EXCEEDED"})," grpc error despite being properly accepted and processed by the server. This PR fixes that issue by redirecting the internal ",(0,i.jsx)(t.code,{children:"rpc WaitForCommand"})," to the new destination for that command."]}),"\n",(0,i.jsx)(t.h3,{id:"rescue-failed-workflows",children:"Rescue Failed Workflows"}),"\n",(0,i.jsxs)(t.p,{children:["PR ",(0,i.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/883",children:"#883"})," allows users to restart failed ",(0,i.jsx)(t.code,{children:"WfRun"}),"'s via the ",(0,i.jsx)(t.code,{children:"lhctl rescue"})," command. This is similar to allowing a user to execute mutating SQL queries via a CLI like ",(0,i.jsx)(t.code,{children:"psql"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["With this feature, a user can fix a buggy Task Worker implementation and then restart a failed ",(0,i.jsx)(t.code,{children:"WfRun"})," and get it to execute the failed ",(0,i.jsx)(t.code,{children:"TaskRun"})," again via:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"lhctl rescue lhctl
Binaries and Release Notes",id:"lhctl-binaries-and-release-notes",level:3},{value:"Reliability during Rebalances",id:"reliability-during-rebalances",level:3},{value:"Rescue Failed Workflows",id:"rescue-failed-workflows",level:3},{value:"mTLS Principals",id:"mtls-principals",level:3},{value:"Dashboard Enhancements",id:"dashboard-enhancements",level:3},{value:"What's Next?",id:"whats-next",level:2}];function h(e){const t={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"0.10"})," release brings with it significant performance and reliability improvements. "]}),"\n",(0,s.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,s.jsxs)(t.h3,{id:"lhctl-binaries-and-release-notes",children:[(0,s.jsx)(t.code,{children:"lhctl"})," Binaries and Release Notes"]}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"0.10.0"})," release comes with a new ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases",children:"Release Page"}),", including ",(0,s.jsx)(t.code,{children:"lhctl"})," binaries built for ARM, Intel, and Windows."]}),"\n",(0,s.jsx)(t.h3,{id:"reliability-during-rebalances",children:"Reliability during Rebalances"}),"\n",(0,s.jsxs)(t.p,{children:["PR ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/872",children:"#872"})," improves the reliability of LittleHorse during Kafka Streams rebalances. Previously, if a write request (eg. ",(0,s.jsx)(t.code,{children:"rpc RunWf"}),') was received just before a rebalance, certain requests would "time out" from the client perspective and return a ',(0,s.jsx)(t.code,{children:"DEADLINE_EXCEEDED"})," grpc error despite being properly accepted and processed by the server. This PR fixes that issue by redirecting the internal ",(0,s.jsx)(t.code,{children:"rpc WaitForCommand"})," to the new destination for that command."]}),"\n",(0,s.jsx)(t.h3,{id:"rescue-failed-workflows",children:"Rescue Failed Workflows"}),"\n",(0,s.jsxs)(t.p,{children:["PR ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/883",children:"#883"})," allows users to restart failed ",(0,s.jsx)(t.code,{children:"WfRun"}),"'s via the ",(0,s.jsx)(t.code,{children:"lhctl rescue"})," command. This is similar to allowing a user to execute mutating SQL queries via a CLI like ",(0,s.jsx)(t.code,{children:"psql"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["With this feature, a user can fix a buggy Task Worker implementation and then restart a failed ",(0,s.jsx)(t.code,{children:"WfRun"})," and get it to execute the failed ",(0,s.jsx)(t.code,{children:"TaskRun"})," again via:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{children:"lhctl rescue PollThread
in Java Task Worker",id:"pollthread-in-java-task-worker",level:3},{value:"What's Next",id:"whats-next",level:2}];function d(e){const t={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,n.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"0.9.2"})," release is now availble and ready for use. The ",(0,r.jsx)(t.code,{children:"0.9.x"})," releases focused mainly on:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Improving the user experience on the LittleHorse Dashboard"}),"\n",(0,r.jsx)(t.li,{children:"Improving the reliability of the LH Server in the face of rebalances and failures."}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(t.p,{children:["While the majority of the improvements in the ",(0,r.jsx)(t.code,{children:"0.9"})," release revolve around performance and stability, several of them are highly visible to the user (especially the new dashboard!)."]}),"\n",(0,r.jsx)(t.h3,{id:"dashboard-rewrite",children:"Dashboard Rewrite"}),"\n",(0,r.jsxs)(t.p,{children:["With help from ",(0,r.jsx)(t.a,{href:"https://github.com/diablouma",children:"Nelson Jumbo"}),", LittleHorse Knight ",(0,r.jsx)(t.a,{href:"https://github.com/mijailrondon",children:"Mija\xedl Rond\xf3n"})," rewrote and revamped our administrative dashboard. It now inclues new features such as:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"User Task Detail page"}),"\n",(0,r.jsxs)(t.li,{children:["Improved details on ",(0,r.jsx)(t.code,{children:"TaskRun"})," progress"]}),"\n",(0,r.jsxs)(t.li,{children:["Improved details on ",(0,r.jsx)(t.code,{children:"WfRun"})," progress"]}),"\n",(0,r.jsx)(t.li,{children:"A plethora of small bug fixes."}),"\n"]}),"\n",(0,r.jsx)(t.h3,{id:"internal-task-queue-optimizations",children:"Internal Task Queue Optimizations"}),"\n",(0,r.jsxs)(t.p,{children:["Deep in the internals of the LittleHorse Server, we implement a Task Queue mechanism to store ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s before they're dispatched to the Task Worker clients. This release included many improvements to stability of the Task Queues."]}),"\n",(0,r.jsxs)(t.p,{children:["Most importantly, our Grumpy Maintainer (Eduwer Camacaro) put a cap on the memory consumption of a single ",(0,r.jsx)(t.code,{children:"TaskDef"}),". Prior to this release, it was possible for poorly-behaved clients to cause an OOM on the server by running millions of workflows which use a ",(0,r.jsx)(t.code,{children:"TaskDef"})," but not executing the resulting ",(0,r.jsx)(t.code,{children:"TaskRun"}),"s. This would cause an un-bounded buildup of ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s in memory until the server crashed."]}),"\n",(0,r.jsxs)(t.p,{children:["After the ",(0,r.jsx)(t.code,{children:"0.9"})," release, any more than 1,000 ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s for a certain ",(0,r.jsx)(t.code,{children:"TaskDef"})," are not loaded into memory but left on disk."]}),"\n",(0,r.jsx)(t.h3,{id:"principal-deletion",children:"Principal Deletion"}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"0.9"})," release includes the ability to delete a ",(0,r.jsx)(t.code,{children:"Principal"}),". The ",(0,r.jsx)(t.code,{children:"rpc DeletePrincipal"})," is smart enough to ensure that there is always at least one Admin ",(0,r.jsx)(t.code,{children:"Principal"})," to prevent a user from locking themselves out of the cluster."]}),"\n",(0,r.jsxs)(t.h3,{id:"pollthread-in-java-task-worker",children:[(0,r.jsx)(t.code,{children:"PollThread"})," in Java Task Worker"]}),"\n",(0,r.jsxs)(t.p,{children:["We refactored the internal implementation of the Java Task Worker so that, for each LH Server in the cluster, the Task Worker creates a single ",(0,r.jsx)(t.code,{children:"PollThread"})," object which is responsible for polling and executing ",(0,r.jsx)(t.code,{children:"TaskRun"}),"s. The ",(0,r.jsx)(t.code,{children:"PollThread"}),"s now poll in parallel, drastically increasing the throughput of a single Java Task Worker."]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"PollThread"})," was introduced in ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/796",children:"#796"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,r.jsxs)(t.p,{children:["Our wire protocol (the GRPC API) is quite stable; there have been no major breaking changes since we introduced the alpha version of Multi-Tenancy in ",(0,r.jsx)(t.code,{children:"0.7"}),". We are diligently proceeding through soak tests, load tests, and chaos tests with our server and we have found and addressed several issues."]}),"\n",(0,r.jsxs)(t.p,{children:["We continue to look foward to the ",(0,r.jsx)(t.code,{children:"1.0"})," release, and we will reach that milestone once:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"We are satisfied with results of load tests and soak tests."}),"\n",(0,r.jsx)(t.li,{children:"We have had language experts review each of our three main SDK's (Java, Go, Python) and we have addressed any change requests."}),"\n",(0,r.jsx)(t.li,{children:"We approach a year without any breaking changes to our wire protocol."}),"\n"]})]})}function c(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>a,x:()=>l});var r=s(6540);const n={},i=r.createContext(n);function a(e){const t=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/8bc86172.76e21eab.js b/assets/js/8bc86172.76e21eab.js
deleted file mode 100644
index 3e183d011..000000000
--- a/assets/js/8bc86172.76e21eab.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[3828],{4298:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>o,contentTitle:()=>a,default:()=>c,frontMatter:()=>n,metadata:()=>l,toc:()=>h});var r=s(4848),i=s(8453);const n={title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},a=void 0,l={permalink:"/blog/littlehorse-0.9-release",source:"@site/blog/2024-06-24-0.9.2-release.md",title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",date:"2024-06-24T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.35,hasTruncateMarker:!0,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.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"},nextItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"}},o={authorsImageUrls:[void 0]},h=[{value:"New Features",id:"new-features",level:2},{value:"Dashboard Rewrite",id:"dashboard-rewrite",level:3},{value:"Internal Task Queue Optimizations",id:"internal-task-queue-optimizations",level:3},{value:"Principal Deletion",id:"principal-deletion",level:3},{value:"PollThread
in Java Task Worker",id:"pollthread-in-java-task-worker",level:3},{value:"What's Next",id:"whats-next",level:2}];function d(e){const t={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"0.9.2"})," release is now availble and ready for use. The ",(0,r.jsx)(t.code,{children:"0.9.x"})," releases focused mainly on:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Improving the user experience on the LittleHorse Dashboard"}),"\n",(0,r.jsx)(t.li,{children:"Improving the reliability of the LH Server in the face of rebalances and failures."}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(t.p,{children:["While the majority of the improvements in the ",(0,r.jsx)(t.code,{children:"0.9"})," release revolve around performance and stability, several of them are highly visible to the user (especially the new dashboard!)."]}),"\n",(0,r.jsx)(t.h3,{id:"dashboard-rewrite",children:"Dashboard Rewrite"}),"\n",(0,r.jsxs)(t.p,{children:["With help from ",(0,r.jsx)(t.a,{href:"https://github.com/diablouma",children:"Nelson Jumbo"}),", LittleHorse Knight ",(0,r.jsx)(t.a,{href:"https://github.com/mijailrondon",children:"Mija\xedl Rond\xf3n"})," rewrote and revamped our administrative dashboard. It now inclues new features such as:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"User Task Detail page"}),"\n",(0,r.jsxs)(t.li,{children:["Improved details on ",(0,r.jsx)(t.code,{children:"TaskRun"})," progress"]}),"\n",(0,r.jsxs)(t.li,{children:["Improved details on ",(0,r.jsx)(t.code,{children:"WfRun"})," progress"]}),"\n",(0,r.jsx)(t.li,{children:"A plethora of small bug fixes."}),"\n"]}),"\n",(0,r.jsx)(t.h3,{id:"internal-task-queue-optimizations",children:"Internal Task Queue Optimizations"}),"\n",(0,r.jsxs)(t.p,{children:["Deep in the internals of the LittleHorse Server, we implement a Task Queue mechanism to store ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s before they're dispatched to the Task Worker clients. This release included many improvements to stability of the Task Queues."]}),"\n",(0,r.jsxs)(t.p,{children:["Most importantly, our Grumpy Maintainer (Eduwer Camacaro) put a cap on the memory consumption of a single ",(0,r.jsx)(t.code,{children:"TaskDef"}),". Prior to this release, it was possible for poorly-behaved clients to cause an OOM on the server by running millions of workflows which use a ",(0,r.jsx)(t.code,{children:"TaskDef"})," but not executing the resulting ",(0,r.jsx)(t.code,{children:"TaskRun"}),"s. This would cause an un-bounded buildup of ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s in memory until the server crashed."]}),"\n",(0,r.jsxs)(t.p,{children:["After the ",(0,r.jsx)(t.code,{children:"0.9"})," release, any more than 1,000 ",(0,r.jsx)(t.code,{children:"ScheduledTask"}),"s for a certain ",(0,r.jsx)(t.code,{children:"TaskDef"})," are not loaded into memory but left on disk."]}),"\n",(0,r.jsx)(t.h3,{id:"principal-deletion",children:"Principal Deletion"}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"0.9"})," release includes the ability to delete a ",(0,r.jsx)(t.code,{children:"Principal"}),". The ",(0,r.jsx)(t.code,{children:"rpc DeletePrincipal"})," is smart enough to ensure that there is always at least one Admin ",(0,r.jsx)(t.code,{children:"Principal"})," to prevent a user from locking themselves out of the cluster."]}),"\n",(0,r.jsxs)(t.h3,{id:"pollthread-in-java-task-worker",children:[(0,r.jsx)(t.code,{children:"PollThread"})," in Java Task Worker"]}),"\n",(0,r.jsxs)(t.p,{children:["We refactored the internal implementation of the Java Task Worker so that, for each LH Server in the cluster, the Task Worker creates a single ",(0,r.jsx)(t.code,{children:"PollThread"})," object which is responsible for polling and executing ",(0,r.jsx)(t.code,{children:"TaskRun"}),"s. The ",(0,r.jsx)(t.code,{children:"PollThread"}),"s now poll in parallel, drastically increasing the throughput of a single Java Task Worker."]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"PollThread"})," was introduced in ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/pull/796",children:"#796"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,r.jsxs)(t.p,{children:["Our wire protocol (the GRPC API) is quite stable; there have been no major breaking changes since we introduced the alpha version of Multi-Tenancy in ",(0,r.jsx)(t.code,{children:"0.7"}),". We are diligently proceeding through soak tests, load tests, and chaos tests with our server and we have found and addressed several issues."]}),"\n",(0,r.jsxs)(t.p,{children:["We continue to look foward to the ",(0,r.jsx)(t.code,{children:"1.0"})," release, and we will reach that milestone once:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"We are satisfied with results of load tests and soak tests."}),"\n",(0,r.jsx)(t.li,{children:"We have had language experts review each of our three main SDK's (Java, Go, Python) and we have addressed any change requests."}),"\n",(0,r.jsx)(t.li,{children:"We approach a year without any breaking changes to our wire protocol."}),"\n"]})]})}function c(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>a,x:()=>l});var r=s(6540);const i={},n=r.createContext(i);function a(e){const t=r.useContext(n);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),r.createElement(n.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/8dbae9ea.2a0dd317.js b/assets/js/8dbae9ea.2a0dd317.js
deleted file mode 100644
index 9f92305d4..000000000
--- a/assets/js/8dbae9ea.2a0dd317.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7316],{7693:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var r=s(4848),n=s(8453);const o={title:"Releasing 0.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.8-release",source:"@site/blog/2024-03-26-0.8.1-release.md",title:"Releasing 0.8",description:"Hardening Security",date:"2024-03-26T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.955,hasTruncateMarker:!0,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.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"},nextItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,n.R)(),...e.components};return(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"0.8"})," release of LittleHorse is out! This pre-1.0 release contains many new features, security enhancements, and performance improvements."]})}function u(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>i,x:()=>a});var r=s(6540);const n={},o=r.createContext(n);function i(e){const t=r.useContext(o);return r.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:i(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/8dbae9ea.4c7f823c.js b/assets/js/8dbae9ea.4c7f823c.js
new file mode 100644
index 000000000..3615a08f4
--- /dev/null
+++ b/assets/js/8dbae9ea.4c7f823c.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7316],{7693:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var n=s(4848),r=s(8453);const i={title:"Releasing 0.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},o=void 0,a={permalink:"/blog/littlehorse-0.8-release",source:"@site/blog/2024-03-26-0.8.1-release.md",title:"Releasing 0.8",description:"Hardening Security",date:"2024-03-26T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.955,hasTruncateMarker:!0,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.8",description:"Hardening Security",slug:"littlehorse-0.8-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.9",permalink:"/blog/littlehorse-0.9-release"},nextItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"}},l={authorsImageUrls:[void 0]},c=[];function u(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"0.8"})," release of LittleHorse is out! This pre-1.0 release contains many new features, security enhancements, and performance improvements."]})}function h(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(u,{...e})}):u(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>o,x:()=>a});var n=s(6540);const r={},i=n.createContext(r);function o(e){const t=n.useContext(i);return n.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(r):e.components||r:o(e.components),n.createElement(i.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/a86a7ed2.5d49792c.js b/assets/js/a86a7ed2.5d49792c.js
deleted file mode 100644
index 5f1710147..000000000
--- a/assets/js/a86a7ed2.5d49792c.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7173],{3773:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var s=o(4848),n=o(8453);const r={title:"Releasing 0.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.2.0-release",source:"@site/blog/2023-08-30-0.2.0-release.md",title:"Releasing 0.2.0",description:"Making workflow development easy again.",date:"2023-08-30T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.54,hasTruncateMarker:!0,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.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,n.R)(),...e.components};return(0,s.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,s.jsx)(t.code,{children:"0.2.0"}),"!"]})}function u(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>i,x:()=>a});var s=o(6540);const n={},r=s.createContext(n);function i(e){const t=s.useContext(r);return s.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:i(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/a86a7ed2.ee6a52f0.js b/assets/js/a86a7ed2.ee6a52f0.js
new file mode 100644
index 000000000..43c4666a2
--- /dev/null
+++ b/assets/js/a86a7ed2.ee6a52f0.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7173],{3773:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var o=s(4848),n=s(8453);const r={title:"Releasing 0.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.2.0-release",source:"@site/blog/2023-08-30-0.2.0-release.md",title:"Releasing 0.2.0",description:"Making workflow development easy again.",date:"2023-08-30T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:3.54,hasTruncateMarker:!0,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.2.0",description:"Making workflow development easy again.",slug:"littlehorse-0.2.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},c=[];function u(e){const t={code:"code",p:"p",...(0,n.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,o.jsx)(t.code,{children:"0.2.0"}),"!"]})}function h(e={}){const{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(u,{...e})}):u(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>i,x:()=>a});var o=s(6540);const n={},r=o.createContext(n);function i(e){const t=o.useContext(r);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:i(e.components),o.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/a957f15c.26e73b5e.js b/assets/js/a957f15c.26e73b5e.js
deleted file mode 100644
index bfdbdfabe..000000000
--- a/assets/js/a957f15c.26e73b5e.js
+++ /dev/null
@@ -1 +0,0 @@
-"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/a957f15c.f75c6662.js b/assets/js/a957f15c.f75c6662.js
new file mode 100644
index 000000000..de2626f40
--- /dev/null
+++ b/assets/js/a957f15c.f75c6662.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[5174],{488:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>f,frontMatter:()=>i,metadata:()=>s,toc:()=>l});var n=o(4848),a=o(8453);const i={slug:"microservices-and-workflow",title:"Microservices and Workflow: A Match Made in Heaven",authors:["coltmcnealy"],tags:["analysis","microservice-and-workflow"]},r=void 0,s={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."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],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","microservice-and-workflow"]},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,n.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 f(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>r,x:()=>s});var n=o(6540);const a={},i=n.createContext(a);function r(e){const t=n.useContext(i);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function s(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),n.createElement(i.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/b136319f.72a954d8.js b/assets/js/b136319f.72a954d8.js
deleted file mode 100644
index 574623c46..000000000
--- a/assets/js/b136319f.72a954d8.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.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/b136319f.fb8d8543.js b/assets/js/b136319f.fb8d8543.js
new file mode 100644
index 000000000..9f0b69344
--- /dev/null
+++ b/assets/js/b136319f.fb8d8543.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7762],{7393:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var s=t(4848),r=t(8453);const i={slug:"saga-pattern",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},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."},{inline:!1,label:"Integration Patterns",permalink:"/blog/tags/integration-patterns/",description:"A 5-part blog series on Integration Patterns that are useful for event-driven systems."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:6.235,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","integration-patterns","littlehorse"]},unlisted:!1,prevItem:{title:"Integration Patterns: Transactional Outbox",permalink:"/blog/transactional-outbox"},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 n={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,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.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,s.jsxs)(n.admonition,{type:"info",children:[(0,s.jsx)(n.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,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"[This Post]"})," Saga Transactions"]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/blog/transactional-outbox",children:"The Transactional Outbox Pattern"})}),"\n",(0,s.jsx)(n.li,{children:"[Coming soon] Queuing and Backpressure"}),"\n",(0,s.jsx)(n.li,{children:"[Coming soon] Retries and Dead-Letter Queues"}),"\n",(0,s.jsx)(n.li,{children:"[Coming soon] Callbacks and External Events"}),"\n"]})]}),"\n",(0,s.jsx)(n.h2,{id:"the-saga-pattern",children:"The Saga Pattern"}),"\n",(0,s.jsxs)(n.p,{children:["At a technical level, the ",(0,s.jsx)(n.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,s.jsx)(n.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,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.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,s.jsx)(n.h3,{id:"use-cases",children:"Use Cases"}),"\n",(0,s.jsx)(n.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,s.jsx)(n.p,{children:"The Saga pattern is appropriate when:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A business process must take action across multiple separate systems (legacy monoliths, microservices, external API's, etc),"}),"\n",(0,s.jsx)(n.li,{children:'Each of those actions can be undone via a "compensation task", and'}),"\n",(0,s.jsx)(n.li,{children:"All actions must logically happen together or not at all."}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["It's also worth noting that a different flavor of the Saga pattern can also be used in ",(0,s.jsx)(n.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,s.jsx)(n.h3,{id:"implementation",children:"Implementation"}),"\n",(0,s.jsx)(n.p,{children:"While Saga is very hard to implement, it's simple to describe:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Try to perform the actions across the multiple systems."}),"\n",(0,s.jsxs)(n.li,{children:["If one of the actions fails, then run a ",(0,s.jsx)(n.em,{children:"compensation"})," for all previously-executed tasks."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.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,s.jsx)(n.h2,{id:"case-study-order-processing",children:"Case Study: Order Processing"}),"\n",(0,s.jsxs)(n.p,{children:["Let's take a look at a familiar use-case: an order processing workflow involving the ",(0,s.jsx)(n.code,{children:"inventory"})," service, and the ",(0,s.jsx)(n.code,{children:"payments"})," service. (The ",(0,s.jsx)(n.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,s.jsx)(n.p,{children:"In this business process, we first reserve inventory for the ordered item. Next, we charge the customer's credit card."}),"\n",(0,s.jsx)(n.p,{children:"If charging the credit card fails, then we have a problem: we've reserved inventory but not sold it."}),"\n",(0,s.jsxs)(n.p,{children:["Our services need the following functionality. In SOA, these would be endpoints; in LittleHorse, they would be ",(0,s.jsx)(n.code,{children:"TaskDef"}),"s:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"create-order"}),": creates an order in the ",(0,s.jsx)(n.code,{children:"PENDING"})," status."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"reserve-inventory"}),": marks an item as no longer available for sale."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"charge-payment"}),": charges the customer."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"release-inventory"}),": marks an item as available for sale again."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"cancel-order"}),": marks an order as ",(0,s.jsx)(n.code,{children:"CANCELED"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"complete-order"}),": marks an order as ",(0,s.jsx)(n.code,{children:"COMPLETED"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-message-queues",children:"Using Message Queues"}),"\n",(0,s.jsx)(n.p,{children:"Using message queues, the happy path looks like the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Architecture diagram",src:t(4719).A+"",width:"544",height:"493"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["The above image assumes the ",(0,s.jsx)(n.em,{children:"choreography"})," pattern, in contrast to the ",(0,s.jsx)(n.em,{children:"orchestrator"})," pattern. The orchestrator pattern is a ton of work and involves writing something that very much resembles LittleHorse!"]})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Orders service calls ",(0,s.jsx)(n.code,{children:"createOrder()"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Orders service publishes to the ",(0,s.jsx)(n.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service reads the message and calls ",(0,s.jsx)(n.code,{children:"reserveInventory()"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service publishes to the ",(0,s.jsx)(n.code,{children:"charge-payment"})," queue."]}),"\n",(0,s.jsx)(n.li,{children:"Payment service charges the credit card."}),"\n",(0,s.jsxs)(n.li,{children:["Payment service publishes to the ",(0,s.jsx)(n.code,{children:"complete-order"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Orders service consumes the record and calls ",(0,s.jsx)(n.code,{children:"completeOrder()"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.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,s.jsx)(n.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,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Architecture Diagram",src:t(8525).A+"",width:"712",height:"493"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Orders service calls ",(0,s.jsx)(n.code,{children:"createOrder()"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Orders service publishes to the ",(0,s.jsx)(n.code,{children:"reserve-inventory"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service reads the message and calls ",(0,s.jsx)(n.code,{children:"reserveInventory()"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service publishes to the ",(0,s.jsx)(n.code,{children:"charge-payment"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Payment service charges the credit card ",(0,s.jsx)(n.em,{children:"unsuccessfully"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Payment service publishes to the ",(0,s.jsx)(n.code,{children:"release-inventory"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service reads the record and calls ",(0,s.jsx)(n.code,{children:"releaseInventory()"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Inventory service publishes to the ",(0,s.jsx)(n.code,{children:"cancel-order"})," queue."]}),"\n",(0,s.jsxs)(n.li,{children:["Orders service consumes the record and calls ",(0,s.jsx)(n.code,{children:"cancelOrder()"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["We still haven't even considered the case when the ",(0,s.jsx)(n.code,{children:"reserve-inventory"})," step fails and we need to catch that exception and handle the order. For the sake of brevity, we will leave that out."]})}),"\n",(0,s.jsxs)(n.p,{children:["Now, we have ",(0,s.jsx)(n.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,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsxs)(n.p,{children:["One thing we are ignoring in this blog post is ",(0,s.jsx)(n.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,s.jsx)(n.h3,{id:"using-littlehorse",children:"Using LittleHorse"}),"\n",(0,s.jsxs)(n.p,{children:["Using LittleHorse, in java, this whole workflow could look like the following. This is ",(0,s.jsx)(n.em,{children:"real code"})," that does indeed compile and replaces the need for all of the complex queueing logic we had above."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.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\n // Saga Here! (We skipped this part in the previous section due to\n // complexity, but LH makes it simple enough.\n NodeOutput inventoryResult = wf.execute("reserve-inventory", item, orderId);\n wf.handleException(inventoryResult, "out-of-stock", handler -> {\n handler.execute("cancel-order", orderId);\n handler.fail("out-of-stock", "Item was out of stock. Order canceled");\n })\n\n NodeOutput paymentResult = wf.execute("charge-payment", customer, price);\n // Saga here again!!\n wf.handleException(paymentResult, "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,s.jsxs)(n.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,s.jsx)(n.em,{children:"truly"})," modular tasks, and let LittleHorse take care of the rest."]}),"\n",(0,s.jsx)(n.h2,{id:"wrapping-up",children:"Wrapping Up"}),"\n",(0,s.jsxs)(n.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,s.jsx)(n.em,{children:"painfully complex"})," to implement. Fortunately, LittleHorse makes it easier!"]}),"\n",(0,s.jsxs)(n.admonition,{type:"note",children:[(0,s.jsxs)(n.p,{children:["A careful reader, or anyone who ",(0,s.jsx)(n.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,s.jsx)(n.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,s.jsx)(n.h3,{id:"get-involved",children:"Get Involved!"}),"\n",(0,s.jsx)(n.p,{children:"Stay tuned for the next post on the Transactional Outbox Pattern! In the meantime:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Try out our ",(0,s.jsx)(n.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"Quickstarts"})]}),"\n",(0,s.jsxs)(n.li,{children:["Join us ",(0,s.jsx)(n.a,{href:"https://launchpass.com/littlehorsecommunity",children:"on Slack"})]}),"\n",(0,s.jsxs)(n.li,{children:["Give us a star ",(0,s.jsx)(n.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"on GitHub"}),"!"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8525:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/2024-09-24-choreography-saga-b400a44518ce7213d491df50bad0bb72.png"},4719:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/2024-09-24-choreography-simple-830e1fcf682eb3cde8b40210f92b3dd2.png"},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>o});var s=t(6540);const r={},i=s.createContext(r);function a(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/b1ad0a29.80b8ec24.js b/assets/js/b1ad0a29.80b8ec24.js
deleted file mode 100644
index 3136824fc..000000000
--- a/assets/js/b1ad0a29.80b8ec24.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[9685],{3095:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>a,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>h});var r=t(4848),n=t(8453);const i={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:!1},o=void 0,l={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:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.215,hasTruncateMarker:!0,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:!1},unlisted:!1,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"}},a={authorsImageUrls:[void 0]},h=[{value:"New Features",id:"new-features",level:2},{value:"Dashboard",id:"dashboard",level:3},{value:"Scheduled Workflows",id:"scheduled-workflows",level:3},{value:"Secret Variables",id:"secret-variables",level:3},{value:"Saving User Task Progress",id:"saving-user-task-progress",level:3},{value:"Release Notes and Artifacts",id:"release-notes-and-artifacts",level:2},{value:"Upgrading",id:"upgrading",level:2},{value:"Upgrading the Go SDK",id:"upgrading-the-go-sdk",level:3},{value:"What's Next?",id:"whats-next",level:2}];function c(e){const s={a:"a",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,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(s.p,{children:["The ",(0,r.jsx)(s.code,{children:"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. "]}),"\n",(0,r.jsx)(s.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(s.p,{children:["In addition to several new features, it's worth calling out that we upgraded the internal ",(0,r.jsx)(s.code,{children:"org.apache.kafka:kafka-streams"})," dependency to ",(0,r.jsx)(s.code,{children:"3.8.0"}),", which includes several crucial bug fixes (some of which were found by our Grumpy Maintainer, ",(0,r.jsx)(s.a,{href:"https://github.com/eduwercamacaro",children:"Eduwer Camacaro"}),")."]}),"\n",(0,r.jsx)(s.h3,{id:"dashboard",children:"Dashboard"}),"\n",(0,r.jsxs)(s.p,{children:["The Dashboard saw several enhancements, the most important of which is the ",(0,r.jsx)(s.code,{children:"ExternalEventDef"})," page, which allows users to view ",(0,r.jsx)(s.code,{children:"ExternalEvent"}),"s associated with an ",(0,r.jsx)(s.code,{children:"ExternalEventDef"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"scheduled-workflows",children:"Scheduled Workflows"}),"\n",(0,r.jsxs)(s.p,{children:["The ",(0,r.jsx)(s.code,{children:"ScheduledWfRun"})," feature creates a schedule that runs a ",(0,r.jsx)(s.code,{children:"WfSpec"})," on a cron schedule. This is useful for periodic background tasks."]}),"\n",(0,r.jsx)(s.h3,{id:"secret-variables",children:"Secret Variables"}),"\n",(0,r.jsxs)(s.p,{children:["As of LittleHorse ",(0,r.jsx)(s.code,{children:"0.11"}),", you may now mark a variable as ",(0,r.jsx)(s.code,{children:"masked()"}),", which means that its value is obscured on the Dashboard and also via ",(0,r.jsx)(s.code,{children:"lhctl get variable"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"To make a variable Masked, you can do the following:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:'WfRunVariable myVar = wf.addVariable("my-var", STR).masked();\n'})}),"\n",(0,r.jsx)(s.p,{children:"We will also release a blog about this feature soon."}),"\n",(0,r.jsx)(s.h3,{id:"saving-user-task-progress",children:"Saving User Task Progress"}),"\n",(0,r.jsxs)(s.p,{children:["With the ",(0,r.jsx)(s.code,{children:"rpc SaveUserTaskRun"}),", it is now possible to save the results of a ",(0,r.jsx)(s.code,{children:"UserTaskRun"})," without completing it. When you do this, an ",(0,r.jsx)(s.code,{children:"event"})," is added to the audit log showing who saved the ",(0,r.jsx)(s.code,{children:"UserTaskRun"})," and what results were saved."]}),"\n",(0,r.jsx)(s.h2,{id:"release-notes-and-artifacts",children:"Release Notes and Artifacts"}),"\n",(0,r.jsx)(s.p,{children:"You can find the release notes and downloads on our GitHub page."}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.2"})})})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.1"})})})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.0"})})})}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"upgrading",children:"Upgrading"}),"\n",(0,r.jsxs)(s.p,{children:["Just as since all releases since ",(0,r.jsx)(s.code,{children:"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,r.jsx)(s.code,{children:"0.11"})," and beyond."]}),"\n",(0,r.jsx)(s.p,{children:"However, we refactored the Go SDK to better follow GoLang conventions, which will require code changes (but no changes to the network protocol)."}),"\n",(0,r.jsx)(s.h3,{id:"upgrading-the-go-sdk",children:"Upgrading the Go SDK"}),"\n",(0,r.jsx)(s.p,{children:"Now, instead of having multiple modules to import and use, there are only two:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"lhproto"})," module, with our GRPC clients and protobuf."]}),"\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"littlehorse"})," module, with everything else."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"To add the go SDK to your project, you can run:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:"go get github.com/littlehorse-enterprises/littlehorse@v0.11.2\n"})}),"\n",(0,r.jsx)(s.p,{children:"Then, the imports are:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-go",children:'import (\n\t"github.com/littlehorse-enterprises/littlehorse/sdk-go/lhproto"\n\t"github.com/littlehorse-enterprises/littlehorse/sdk-go/littlehorse"\n)\n'})}),"\n",(0,r.jsx)(s.h2,{id:"whats-next",children:"What's Next?"}),"\n",(0,r.jsxs)(s.p,{children:["Before committing to ",(0,r.jsx)(s.a,{href:"https://semver.org",children:"Semantic Versioning"}),", we will:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Release our release schedule and support plan."}),"\n",(0,r.jsxs)(s.li,{children:["Finish inspecting our SDK's for bugs and minor breaking API changes that we want to do before ",(0,r.jsx)(s.code,{children:"1.0"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Finish our benchmarks, chaos tests, and load tests to ensure that our software meets the highest quality standards."}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["We expect to release ",(0,r.jsx)(s.code,{children:"1.0"})," in early October 2024."]})]})}function d(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},8453:(e,s,t)=>{t.d(s,{R:()=>o,x:()=>l});var r=t(6540);const n={},i=r.createContext(n);function o(e){const s=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:o(e.components),r.createElement(i.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/b1ad0a29.9cdbe99c.js b/assets/js/b1ad0a29.9cdbe99c.js
new file mode 100644
index 000000000..e695b79d4
--- /dev/null
+++ b/assets/js/b1ad0a29.9cdbe99c.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[9685],{3095:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>a,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>h});var r=t(4848),n=t(8453);const i={title:"Releasing 0.11",description:"Releasing LittleHorse `0.11`",slug:"littlehorse-0.11-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},o=void 0,l={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:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.215,hasTruncateMarker:!0,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:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,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"}},a={authorsImageUrls:[void 0]},h=[{value:"New Features",id:"new-features",level:2},{value:"Dashboard",id:"dashboard",level:3},{value:"Scheduled Workflows",id:"scheduled-workflows",level:3},{value:"Secret Variables",id:"secret-variables",level:3},{value:"Saving User Task Progress",id:"saving-user-task-progress",level:3},{value:"Release Notes and Artifacts",id:"release-notes-and-artifacts",level:2},{value:"Upgrading",id:"upgrading",level:2},{value:"Upgrading the Go SDK",id:"upgrading-the-go-sdk",level:3},{value:"What's Next?",id:"whats-next",level:2}];function c(e){const s={a:"a",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,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(s.p,{children:["The ",(0,r.jsx)(s.code,{children:"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. "]}),"\n",(0,r.jsx)(s.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(s.p,{children:["In addition to several new features, it's worth calling out that we upgraded the internal ",(0,r.jsx)(s.code,{children:"org.apache.kafka:kafka-streams"})," dependency to ",(0,r.jsx)(s.code,{children:"3.8.0"}),", which includes several crucial bug fixes (some of which were found by our Grumpy Maintainer, ",(0,r.jsx)(s.a,{href:"https://github.com/eduwercamacaro",children:"Eduwer Camacaro"}),")."]}),"\n",(0,r.jsx)(s.h3,{id:"dashboard",children:"Dashboard"}),"\n",(0,r.jsxs)(s.p,{children:["The Dashboard saw several enhancements, the most important of which is the ",(0,r.jsx)(s.code,{children:"ExternalEventDef"})," page, which allows users to view ",(0,r.jsx)(s.code,{children:"ExternalEvent"}),"s associated with an ",(0,r.jsx)(s.code,{children:"ExternalEventDef"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"scheduled-workflows",children:"Scheduled Workflows"}),"\n",(0,r.jsxs)(s.p,{children:["The ",(0,r.jsx)(s.code,{children:"ScheduledWfRun"})," feature creates a schedule that runs a ",(0,r.jsx)(s.code,{children:"WfSpec"})," on a cron schedule. This is useful for periodic background tasks."]}),"\n",(0,r.jsx)(s.h3,{id:"secret-variables",children:"Secret Variables"}),"\n",(0,r.jsxs)(s.p,{children:["As of LittleHorse ",(0,r.jsx)(s.code,{children:"0.11"}),", you may now mark a variable as ",(0,r.jsx)(s.code,{children:"masked()"}),", which means that its value is obscured on the Dashboard and also via ",(0,r.jsx)(s.code,{children:"lhctl get variable"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"To make a variable Masked, you can do the following:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:'WfRunVariable myVar = wf.addVariable("my-var", STR).masked();\n'})}),"\n",(0,r.jsx)(s.p,{children:"We will also release a blog about this feature soon."}),"\n",(0,r.jsx)(s.h3,{id:"saving-user-task-progress",children:"Saving User Task Progress"}),"\n",(0,r.jsxs)(s.p,{children:["With the ",(0,r.jsx)(s.code,{children:"rpc SaveUserTaskRun"}),", it is now possible to save the results of a ",(0,r.jsx)(s.code,{children:"UserTaskRun"})," without completing it. When you do this, an ",(0,r.jsx)(s.code,{children:"event"})," is added to the audit log showing who saved the ",(0,r.jsx)(s.code,{children:"UserTaskRun"})," and what results were saved."]}),"\n",(0,r.jsx)(s.h2,{id:"release-notes-and-artifacts",children:"Release Notes and Artifacts"}),"\n",(0,r.jsx)(s.p,{children:"You can find the release notes and downloads on our GitHub page."}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.2"})})})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.1"})})})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/releases/tag/v0.11.2",children:(0,r.jsx)(s.strong,{children:(0,r.jsx)(s.code,{children:"0.11.0"})})})}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"upgrading",children:"Upgrading"}),"\n",(0,r.jsxs)(s.p,{children:["Just as since all releases since ",(0,r.jsx)(s.code,{children:"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,r.jsx)(s.code,{children:"0.11"})," and beyond."]}),"\n",(0,r.jsx)(s.p,{children:"However, we refactored the Go SDK to better follow GoLang conventions, which will require code changes (but no changes to the network protocol)."}),"\n",(0,r.jsx)(s.h3,{id:"upgrading-the-go-sdk",children:"Upgrading the Go SDK"}),"\n",(0,r.jsx)(s.p,{children:"Now, instead of having multiple modules to import and use, there are only two:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"lhproto"})," module, with our GRPC clients and protobuf."]}),"\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"littlehorse"})," module, with everything else."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"To add the go SDK to your project, you can run:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:"go get github.com/littlehorse-enterprises/littlehorse@v0.11.2\n"})}),"\n",(0,r.jsx)(s.p,{children:"Then, the imports are:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-go",children:'import (\n\t"github.com/littlehorse-enterprises/littlehorse/sdk-go/lhproto"\n\t"github.com/littlehorse-enterprises/littlehorse/sdk-go/littlehorse"\n)\n'})}),"\n",(0,r.jsx)(s.h2,{id:"whats-next",children:"What's Next?"}),"\n",(0,r.jsxs)(s.p,{children:["Before committing to ",(0,r.jsx)(s.a,{href:"https://semver.org",children:"Semantic Versioning"}),", we will:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Release our release schedule and support plan."}),"\n",(0,r.jsxs)(s.li,{children:["Finish inspecting our SDK's for bugs and minor breaking API changes that we want to do before ",(0,r.jsx)(s.code,{children:"1.0"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Finish our benchmarks, chaos tests, and load tests to ensure that our software meets the highest quality standards."}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["We expect to release ",(0,r.jsx)(s.code,{children:"1.0"})," in early October 2024."]})]})}function d(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},8453:(e,s,t)=>{t.d(s,{R:()=>o,x:()=>l});var r=t(6540);const n={},i=r.createContext(n);function o(e){const s=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:o(e.components),r.createElement(i.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/b770e739.c86f3b46.js b/assets/js/b770e739.c86f3b46.js
new file mode 100644
index 000000000..9f1ad8ea9
--- /dev/null
+++ b/assets/js/b770e739.c86f3b46.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[9615],{7100:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>i,toc:()=>c});var a=n(4848),o=n(8453);const s={slug:"transactional-outbox",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},r="Integration Patterns: Transactional Outbox",i={permalink:"/blog/transactional-outbox",source:"@site/blog/2024-09-30-transactional-outbox.md",title:"Integration Patterns: Transactional Outbox",description:"Like the Saga Pattern, the Transactional Outbox pattern is tool for defending against data loss in your applications. In this blog we cover how it works and how to do it easier using LittleHorse.",date:"2024-09-30T00: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:"Integration Patterns",permalink:"/blog/tags/integration-patterns/",description:"A 5-part blog series on Integration Patterns that are useful for event-driven systems."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:5.74,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:"transactional-outbox",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},unlisted:!1,nextItem:{title:"Integration Patterns: Saga Transactions",permalink:"/blog/saga-pattern"}},l={authorsImageUrls:[void 0]},c=[];function u(t){const e={a:"a",em:"em",p:"p",...(0,o.R)(),...t.components};return(0,a.jsxs)(e.p,{children:["Like the ",(0,a.jsx)(e.a,{href:"/blog/saga-pattern",children:"Saga Pattern"}),", the Transactional Outbox pattern is tool for defending against data loss in your applications. In this blog we cover how it works and how to do it ",(0,a.jsx)(e.em,{children:"easier"})," using LittleHorse."]})}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/bd28587a.897b6e85.js b/assets/js/bd28587a.897b6e85.js
deleted file mode 100644
index fb927d576..000000000
--- a/assets/js/bd28587a.897b6e85.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[3408],{2702:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>u,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var o=s(4848),r=s(8453);const i={title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},n=void 0,a={permalink:"/blog/littlehorse-0.9-release",source:"@site/blog/2024-06-24-0.9.2-release.md",title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",date:"2024-06-24T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.35,hasTruncateMarker:!0,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.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"},nextItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["The ",(0,o.jsx)(t.code,{children:"0.9.2"})," release is now availble and ready for use."]})}function u(e={}){const{wrapper:t}={...(0,r.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:()=>n,x:()=>a});var o=s(6540);const r={},i=o.createContext(r);function n(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(r):e.components||r:n(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/bd28587a.f68ac721.js b/assets/js/bd28587a.f68ac721.js
new file mode 100644
index 000000000..4d8e4b7b3
--- /dev/null
+++ b/assets/js/bd28587a.f68ac721.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[3408],{2702:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>n,metadata:()=>a,toc:()=>c});var o=s(4848),r=s(8453);const n={title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},i=void 0,a={permalink:"/blog/littlehorse-0.9-release",source:"@site/blog/2024-06-24-0.9.2-release.md",title:"Releasing 0.9",description:"Revamping the LittleHorse Dashboard",date:"2024-06-24T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:2.35,hasTruncateMarker:!0,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.9",description:"Revamping the LittleHorse Dashboard",slug:"littlehorse-0.9-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.10",permalink:"/blog/littlehorse-0.10-release"},nextItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"}},l={authorsImageUrls:[void 0]},c=[];function h(e){const t={code:"code",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(t.p,{children:["The ",(0,o.jsx)(t.code,{children:"0.9.2"})," release is now availble and ready for use."]})}function u(e={}){const{wrapper:t}={...(0,r.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:()=>i,x:()=>a});var o=s(6540);const r={},n=o.createContext(r);function i(e){const t=o.useContext(n);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(r):e.components||r:i(e.components),o.createElement(n.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/c15d9823.1cab491e.js b/assets/js/c15d9823.55c527c7.js
similarity index 80%
rename from assets/js/c15d9823.1cab491e.js
rename to assets/js/c15d9823.55c527c7.js
index f1dbada53..b6beae1e5 100644
--- a/assets/js/c15d9823.1cab491e.js
+++ b/assets/js/c15d9823.55c527c7.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":12,"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":13,"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/d5e335f6.8e6b2248.js b/assets/js/d5e335f6.8e6b2248.js
deleted file mode 100644
index 33a806f62..000000000
--- a/assets/js/d5e335f6.8e6b2248.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7130],{6e3:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var s=n(4848),i=n(8453);const r={title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},o=void 0,l={permalink:"/blog/littlehorse-0.5.0-release",source:"@site/blog/2023-09-08-0.5.0-release.md",title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",date:"2023-09-08T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:5.205,hasTruncateMarker:!0,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.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"},nextItem:{title:"Releasing 0.2.0",permalink:"/blog/littlehorse-0.2.0-release"}},a={authorsImageUrls:[void 0]},c=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"Python WfSpec
Support",id:"python-wfspec-support",level:3},{value:"For-Each Suppport",id:"for-each-suppport",level:3},{value:"Improved Failure Handling",id:"improved-failure-handling",level:3},{value:"LH Server Monitoring",id:"lh-server-monitoring",level:3},{value:"LH Platform",id:"lh-platform",level:3},{value:"Persistent Variables",id:"persistent-variables",level:3},{value:"What's Next",id:"whats-next",level:2},{value:"What about 0.3.0
and 0.4.0
?",id:"what-about-030-and-040",level:3}];function d(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["We are excited to announce the minor release ",(0,s.jsx)(t.code,{children:"0.5.0"}),". This release is highlighted by:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Alpha support for building ",(0,s.jsx)(t.code,{children:"WfSpec"}),"s in Python."]}),"\n",(0,s.jsx)(t.li,{children:"Improved monitoring and health metrics on the LittleHorse Server."}),"\n",(0,s.jsxs)(t.li,{children:["Support for looping over a ",(0,s.jsx)(t.code,{children:"JSON_ARR"})," and launching threads in parallel for each element."]}),"\n",(0,s.jsx)(t.li,{children:"Improved Exception Handling."}),"\n",(0,s.jsx)(t.li,{children:"Limited early access for LittleHorse Platform."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["In this release, we made great strides towards full Python support, improved monitoring and observability, and added the ability to spawn threads in parallel looping over a ",(0,s.jsx)(t.code,{children:"JSON_ARR"})," variable."]}),"\n",(0,s.jsx)(t.h2,{id:"get-started",children:"Get Started"}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,s.jsx)(t.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Try our ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"quickstarts"})]}),"\n",(0,s.jsxs)(t.li,{children:["Visit us on ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"GitHub"})," and give us a ","\u2b50","!"]}),"\n",(0,s.jsxs)(t.li,{children:["Download our ",(0,s.jsx)(t.a,{href:"https://gallery.ecr.aws/littlehorse",children:"docker images"})]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,s.jsxs)(t.p,{children:["We'd like to highlight some of the exciting new features in ",(0,s.jsx)(t.code,{children:"0.5.0"}),"."]}),"\n",(0,s.jsxs)(t.h3,{id:"python-wfspec-support",children:["Python ",(0,s.jsx)(t.code,{children:"WfSpec"})," Support"]}),"\n",(0,s.jsxs)(t.p,{children:["Our Python SDK now has full support for building ",(0,s.jsx)(t.code,{children:"WfSpec"}),"s! You can check it out at our ",(0,s.jsx)(t.a,{href:"/docs/developer-guide/install",children:"quickstart page"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"for-each-suppport",children:"For-Each Suppport"}),"\n",(0,s.jsxs)(t.p,{children:["This is a very exciting feature which allows you to iterate over a list and spawn multiple ",(0,s.jsx)(t.code,{children:"ThreadRun"}),"s (like threads in a program)."]}),"\n",(0,s.jsxs)(t.p,{children:["To see it in action, check out our ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/examples/spawn-thread-foreach",children:"example"})," or read the ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/wfspec-development/child-threads",children:"documentation"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"improved-failure-handling",children:"Improved Failure Handling"}),"\n",(0,s.jsxs)(t.p,{children:["This release introduces a new status for LittleHorse, called ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),". The ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," status differs from the ",(0,s.jsx)(t.code,{children:"ERROR"})," status in the following ways:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"ERROR"})," means an unexpected ",(0,s.jsx)(t.em,{children:"technical"})," failure occurred. For example, a ",(0,s.jsx)(t.code,{children:"TaskRun"})," timed out because a third-party API was down."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"EXCEPTION"})," means that a failure occurred at the ",(0,s.jsx)(t.em,{children:"business process level"}),". For example, you might use an ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," when a customer has insufficient funds in her account to complete an order."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Just like in programming, you can throw and catch ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),"s (and you can also catch ",(0,s.jsx)(t.code,{children:"ERROR"}),"s). For a blog post that goes in-depth into how LittleHorse makes it easy to handle failures in your workflows, check out our ",(0,s.jsx)(t.a,{href:"/docs/concepts/workflows#failure-handling",children:"Failure Handling Docs"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"lh-server-monitoring",children:"LH Server Monitoring"}),"\n",(0,s.jsxs)(t.p,{children:["We added a new path ",(0,s.jsx)(t.code,{children:"/status"})," on the LH Server's health endpoint (port ",(0,s.jsx)(t.code,{children:"1822"})," by default) which can be used to inspect the status of all internal Kafka Streams ",(0,s.jsx)(t.code,{children:"Task"}),"s on the LH Server. It presents the following information:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"All Active Tasks on the host"}),"\n",(0,s.jsx)(t.li,{children:"All Standby Tasks on the host"}),"\n",(0,s.jsx)(t.li,{children:"Any ongoing State Restorations on the host"}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Additionally, we added a ",(0,s.jsx)(t.code,{children:"/diskUsage"})," endpoint which returns the number of bytes of disk space in use by the LH Server."]}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform uses these endpoints to intelligently scale, manage, and operate LittleHorse for you."}),"\n",(0,s.jsx)(t.p,{children:"We are also in the process of writing and implementing a Kafka Improvement Proposal to improve visibility of Standby Tasks, which will allow the LittleHorse Operator (both in LH Platform and LH Cloud) to safely and smoothly scale LittleHorse clusters down without any downtime. Stay tuned in the Kafka developer mailing list!"}),"\n",(0,s.jsx)(t.h3,{id:"lh-platform",children:"LH Platform"}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform is a Kubernetes Operator that securely manages a LittleHorse cluster for you in your own environment. It seamlessly integrates with your Kubernetes environment, GitOps workflows, and security strategy (TLS, mTLS, OAuth, Cert Manager, Keycloak)."}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform is now available for limited early access, and has been installed in one of the largest health insurance companies in the US."}),"\n",(0,s.jsxs)(t.p,{children:["To get started with LittleHorse Platform, please ",(0,s.jsx)(t.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform?usp=sf_link",children:"contact us"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"persistent-variables",children:"Persistent Variables"}),"\n",(0,s.jsxs)(t.p,{children:["In LittleHorse ",(0,s.jsx)(t.code,{children:"0.2.0"})," and later, you can search for ",(0,s.jsx)(t.code,{children:"Variable"}),"s by their value. For example, if you have a Workflow Specification that defines a variable ",(0,s.jsx)(t.code,{children:"email_address"}),", you can find all Workflow Run's where ",(0,s.jsx)(t.code,{children:"email_address == 'obiwan@jedi-council.org"})," by using the ",(0,s.jsx)(t.code,{children:"SearchVariable"})," rpc call."]}),"\n",(0,s.jsxs)(t.p,{children:["The problem with ",(0,s.jsx)(t.code,{children:"0.2.0"}),"? You need to provide the ",(0,s.jsx)(t.code,{children:"wfSpecVersion"})," in your search request. That means you can only search for a ",(0,s.jsx)(t.code,{children:"Variable"})," if you know the version of the ",(0,s.jsx)(t.code,{children:"WfSpec"})," it came from."]}),"\n",(0,s.jsxs)(t.p,{children:["Release ",(0,s.jsx)(t.code,{children:"0.4.0"})," introduced the ability to mark a ",(0,s.jsx)(t.code,{children:"Variable"})," as ",(0,s.jsx)(t.code,{children:"persistent"}),", which means that:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Every future version of the ",(0,s.jsx)(t.code,{children:"WfSpec"})," must have the same variable definition with the same index type."]}),"\n",(0,s.jsxs)(t.li,{children:["You can now search for variables with a certain value across ",(0,s.jsx)(t.em,{children:"all versions"})," of the ",(0,s.jsx)(t.code,{children:"WfSpec"}),"."]}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"Be on the lookout for an upcoming blog post about using Persistent Variables and a simple backend-for-frontend to build an end-to-end Approval Workflow Application using only LittleHorse!"}),"\n",(0,s.jsx)(t.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,s.jsx)(t.p,{children:"Over the next few weeks, we plan to:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Add utilities to make it easier to work with the LittleHorse API."}),"\n",(0,s.jsxs)(t.li,{children:["Allow users to throw a Workflow ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," from within the Task Worker SDK (currently, only ",(0,s.jsx)(t.code,{children:"ERROR"})," is supported)."]}),"\n",(0,s.jsx)(t.li,{children:"Continue hardening the LittleHorse Server's availability and performance story."}),"\n",(0,s.jsx)(t.li,{children:"Launch limited early accesss for LittleHorse Cloud and LittleHorse UI."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["To get started with LittleHorse, head over to our ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"installation docs"}),"."]}),"\n",(0,s.jsxs)(t.h3,{id:"what-about-030-and-040",children:["What about ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),"?"]}),"\n",(0,s.jsxs)(t.p,{children:["We also released ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"})," over the past 5 weeks! (And before ",(0,s.jsx)(t.code,{children:"0.3.0"}),", we had a minor patch bugfix on ",(0,s.jsx)(t.code,{children:"0.2.1"}),")."]}),"\n",(0,s.jsxs)(t.p,{children:["The only thing missing with ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),' is a blog post + announcement. That\'s because a lot of the features we included in this announcement were partially-implemented, implemented in some languages and not others, or in the "experimental" phase at the time of ',(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),". We accelerated the release of ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"})," because certain early-access customers requested certain features on an accelerated timeline."]}),"\n",(0,s.jsxs)(t.p,{children:["As our API is mostly stable now, we will slow down our release cadence to likely a new ",(0,s.jsx)(t.code,{children:"*.x.*"})," version (a ",(0,s.jsx)(t.code,{children:"minor"})," release in ",(0,s.jsx)(t.a,{href:"https://semver.org",children:"Semantic Versioning"}),") every two months, with security and bugfix patch releases (",(0,s.jsx)(t.code,{children:"*.*.x"}),") as needed."]}),"\n",(0,s.jsx)(t.p,{children:"Additionally, as we introduce new features, we will start a release changelog document in which we document the level of stability of the new API's introduced. For example:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"STABLE"}),": Any changes to this API before the next ",(0,s.jsx)(t.a,{href:"https://semver.org",children:"Major Release"})," will be backwards compatible. The feature is covered by our integration tests."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"BETA"}),": We don't anticipate any ",(0,s.jsx)(t.em,{children:"large breaking changes"})," to the feature/API. It is covered by our integration tests, but it ",(0,s.jsx)(t.em,{children:"might"})," change before the ",(0,s.jsx)(t.code,{children:"1.0.0"})," release."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"EXPERIMENTAL"}),": Try it out and give us feedback! But you might want to wait a release or two before putting it into production."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"0.6.0"})," release notes will include a table of all of our features and their API Stability Level in all four of our SDK's."]})]})}function h(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>o,x:()=>l});var s=n(6540);const i={},r=s.createContext(i);function o(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/d5e335f6.91c474a6.js b/assets/js/d5e335f6.91c474a6.js
new file mode 100644
index 000000000..9b7722969
--- /dev/null
+++ b/assets/js/d5e335f6.91c474a6.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[7130],{6e3:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var s=n(4848),i=n(8453);const r={title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},o=void 0,l={permalink:"/blog/littlehorse-0.5.0-release",source:"@site/blog/2023-09-08-0.5.0-release.md",title:"Releasing 0.5.0",description:"Python, For-Each, LH Platform.",date:"2023-09-08T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:5.205,hasTruncateMarker:!0,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.5.0",description:"Python, For-Each, LH Platform.",slug:"littlehorse-0.5.0-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.7",permalink:"/blog/littlehorse-0.7-release"},nextItem:{title:"Releasing 0.2.0",permalink:"/blog/littlehorse-0.2.0-release"}},a={authorsImageUrls:[void 0]},c=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"Python WfSpec
Support",id:"python-wfspec-support",level:3},{value:"For-Each Suppport",id:"for-each-suppport",level:3},{value:"Improved Failure Handling",id:"improved-failure-handling",level:3},{value:"LH Server Monitoring",id:"lh-server-monitoring",level:3},{value:"LH Platform",id:"lh-platform",level:3},{value:"Persistent Variables",id:"persistent-variables",level:3},{value:"What's Next",id:"whats-next",level:2},{value:"What about 0.3.0
and 0.4.0
?",id:"what-about-030-and-040",level:3}];function d(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["We are excited to announce the minor release ",(0,s.jsx)(t.code,{children:"0.5.0"}),". This release is highlighted by:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Alpha support for building ",(0,s.jsx)(t.code,{children:"WfSpec"}),"s in Python."]}),"\n",(0,s.jsx)(t.li,{children:"Improved monitoring and health metrics on the LittleHorse Server."}),"\n",(0,s.jsxs)(t.li,{children:["Support for looping over a ",(0,s.jsx)(t.code,{children:"JSON_ARR"})," and launching threads in parallel for each element."]}),"\n",(0,s.jsx)(t.li,{children:"Improved Exception Handling."}),"\n",(0,s.jsx)(t.li,{children:"Limited early access for LittleHorse Platform."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["In this release, we made great strides towards full Python support, improved monitoring and observability, and added the ability to spawn threads in parallel looping over a ",(0,s.jsx)(t.code,{children:"JSON_ARR"})," variable."]}),"\n",(0,s.jsx)(t.h2,{id:"get-started",children:"Get Started"}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,s.jsx)(t.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Try our ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"quickstarts"})]}),"\n",(0,s.jsxs)(t.li,{children:["Visit us on ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse",children:"GitHub"})," and give us a ","\u2b50","!"]}),"\n",(0,s.jsxs)(t.li,{children:["Download our ",(0,s.jsx)(t.a,{href:"https://gallery.ecr.aws/littlehorse",children:"docker images"})]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,s.jsxs)(t.p,{children:["We'd like to highlight some of the exciting new features in ",(0,s.jsx)(t.code,{children:"0.5.0"}),"."]}),"\n",(0,s.jsxs)(t.h3,{id:"python-wfspec-support",children:["Python ",(0,s.jsx)(t.code,{children:"WfSpec"})," Support"]}),"\n",(0,s.jsxs)(t.p,{children:["Our Python SDK now has full support for building ",(0,s.jsx)(t.code,{children:"WfSpec"}),"s! You can check it out at our ",(0,s.jsx)(t.a,{href:"/docs/developer-guide/install",children:"quickstart page"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"for-each-suppport",children:"For-Each Suppport"}),"\n",(0,s.jsxs)(t.p,{children:["This is a very exciting feature which allows you to iterate over a list and spawn multiple ",(0,s.jsx)(t.code,{children:"ThreadRun"}),"s (like threads in a program)."]}),"\n",(0,s.jsxs)(t.p,{children:["To see it in action, check out our ",(0,s.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/tree/master/examples/spawn-thread-foreach",children:"example"})," or read the ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/wfspec-development/child-threads",children:"documentation"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"improved-failure-handling",children:"Improved Failure Handling"}),"\n",(0,s.jsxs)(t.p,{children:["This release introduces a new status for LittleHorse, called ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),". The ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," status differs from the ",(0,s.jsx)(t.code,{children:"ERROR"})," status in the following ways:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"ERROR"})," means an unexpected ",(0,s.jsx)(t.em,{children:"technical"})," failure occurred. For example, a ",(0,s.jsx)(t.code,{children:"TaskRun"})," timed out because a third-party API was down."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"EXCEPTION"})," means that a failure occurred at the ",(0,s.jsx)(t.em,{children:"business process level"}),". For example, you might use an ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," when a customer has insufficient funds in her account to complete an order."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Just like in programming, you can throw and catch ",(0,s.jsx)(t.code,{children:"EXCEPTION"}),"s (and you can also catch ",(0,s.jsx)(t.code,{children:"ERROR"}),"s). For a blog post that goes in-depth into how LittleHorse makes it easy to handle failures in your workflows, check out our ",(0,s.jsx)(t.a,{href:"/docs/concepts/workflows#failure-handling",children:"Failure Handling Docs"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"lh-server-monitoring",children:"LH Server Monitoring"}),"\n",(0,s.jsxs)(t.p,{children:["We added a new path ",(0,s.jsx)(t.code,{children:"/status"})," on the LH Server's health endpoint (port ",(0,s.jsx)(t.code,{children:"1822"})," by default) which can be used to inspect the status of all internal Kafka Streams ",(0,s.jsx)(t.code,{children:"Task"}),"s on the LH Server. It presents the following information:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"All Active Tasks on the host"}),"\n",(0,s.jsx)(t.li,{children:"All Standby Tasks on the host"}),"\n",(0,s.jsx)(t.li,{children:"Any ongoing State Restorations on the host"}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Additionally, we added a ",(0,s.jsx)(t.code,{children:"/diskUsage"})," endpoint which returns the number of bytes of disk space in use by the LH Server."]}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform uses these endpoints to intelligently scale, manage, and operate LittleHorse for you."}),"\n",(0,s.jsx)(t.p,{children:"We are also in the process of writing and implementing a Kafka Improvement Proposal to improve visibility of Standby Tasks, which will allow the LittleHorse Operator (both in LH Platform and LH Cloud) to safely and smoothly scale LittleHorse clusters down without any downtime. Stay tuned in the Kafka developer mailing list!"}),"\n",(0,s.jsx)(t.h3,{id:"lh-platform",children:"LH Platform"}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform is a Kubernetes Operator that securely manages a LittleHorse cluster for you in your own environment. It seamlessly integrates with your Kubernetes environment, GitOps workflows, and security strategy (TLS, mTLS, OAuth, Cert Manager, Keycloak)."}),"\n",(0,s.jsx)(t.p,{children:"LittleHorse Platform is now available for limited early access, and has been installed in one of the largest health insurance companies in the US."}),"\n",(0,s.jsxs)(t.p,{children:["To get started with LittleHorse Platform, please ",(0,s.jsx)(t.a,{href:"https://docs.google.com/forms/d/e/1FAIpQLScXVvTYy4LQnYoFoRKRQ7ppuxe0KgncsDukvm96qKN0pU5TnQ/viewform?usp=sf_link",children:"contact us"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"persistent-variables",children:"Persistent Variables"}),"\n",(0,s.jsxs)(t.p,{children:["In LittleHorse ",(0,s.jsx)(t.code,{children:"0.2.0"})," and later, you can search for ",(0,s.jsx)(t.code,{children:"Variable"}),"s by their value. For example, if you have a Workflow Specification that defines a variable ",(0,s.jsx)(t.code,{children:"email_address"}),", you can find all Workflow Run's where ",(0,s.jsx)(t.code,{children:"email_address == 'obiwan@jedi-council.org"})," by using the ",(0,s.jsx)(t.code,{children:"SearchVariable"})," rpc call."]}),"\n",(0,s.jsxs)(t.p,{children:["The problem with ",(0,s.jsx)(t.code,{children:"0.2.0"}),"? You need to provide the ",(0,s.jsx)(t.code,{children:"wfSpecVersion"})," in your search request. That means you can only search for a ",(0,s.jsx)(t.code,{children:"Variable"})," if you know the version of the ",(0,s.jsx)(t.code,{children:"WfSpec"})," it came from."]}),"\n",(0,s.jsxs)(t.p,{children:["Release ",(0,s.jsx)(t.code,{children:"0.4.0"})," introduced the ability to mark a ",(0,s.jsx)(t.code,{children:"Variable"})," as ",(0,s.jsx)(t.code,{children:"persistent"}),", which means that:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["Every future version of the ",(0,s.jsx)(t.code,{children:"WfSpec"})," must have the same variable definition with the same index type."]}),"\n",(0,s.jsxs)(t.li,{children:["You can now search for variables with a certain value across ",(0,s.jsx)(t.em,{children:"all versions"})," of the ",(0,s.jsx)(t.code,{children:"WfSpec"}),"."]}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"Be on the lookout for an upcoming blog post about using Persistent Variables and a simple backend-for-frontend to build an end-to-end Approval Workflow Application using only LittleHorse!"}),"\n",(0,s.jsx)(t.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,s.jsx)(t.p,{children:"Over the next few weeks, we plan to:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Add utilities to make it easier to work with the LittleHorse API."}),"\n",(0,s.jsxs)(t.li,{children:["Allow users to throw a Workflow ",(0,s.jsx)(t.code,{children:"EXCEPTION"})," from within the Task Worker SDK (currently, only ",(0,s.jsx)(t.code,{children:"ERROR"})," is supported)."]}),"\n",(0,s.jsx)(t.li,{children:"Continue hardening the LittleHorse Server's availability and performance story."}),"\n",(0,s.jsx)(t.li,{children:"Launch limited early accesss for LittleHorse Cloud and LittleHorse UI."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["To get started with LittleHorse, head over to our ",(0,s.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install",children:"installation docs"}),"."]}),"\n",(0,s.jsxs)(t.h3,{id:"what-about-030-and-040",children:["What about ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),"?"]}),"\n",(0,s.jsxs)(t.p,{children:["We also released ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"})," over the past 5 weeks! (And before ",(0,s.jsx)(t.code,{children:"0.3.0"}),", we had a minor patch bugfix on ",(0,s.jsx)(t.code,{children:"0.2.1"}),")."]}),"\n",(0,s.jsxs)(t.p,{children:["The only thing missing with ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),' is a blog post + announcement. That\'s because a lot of the features we included in this announcement were partially-implemented, implemented in some languages and not others, or in the "experimental" phase at the time of ',(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"}),". We accelerated the release of ",(0,s.jsx)(t.code,{children:"0.3.0"})," and ",(0,s.jsx)(t.code,{children:"0.4.0"})," because certain early-access customers requested certain features on an accelerated timeline."]}),"\n",(0,s.jsxs)(t.p,{children:["As our API is mostly stable now, we will slow down our release cadence to likely a new ",(0,s.jsx)(t.code,{children:"*.x.*"})," version (a ",(0,s.jsx)(t.code,{children:"minor"})," release in ",(0,s.jsx)(t.a,{href:"https://semver.org",children:"Semantic Versioning"}),") every two months, with security and bugfix patch releases (",(0,s.jsx)(t.code,{children:"*.*.x"}),") as needed."]}),"\n",(0,s.jsx)(t.p,{children:"Additionally, as we introduce new features, we will start a release changelog document in which we document the level of stability of the new API's introduced. For example:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"STABLE"}),": Any changes to this API before the next ",(0,s.jsx)(t.a,{href:"https://semver.org",children:"Major Release"})," will be backwards compatible. The feature is covered by our integration tests."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"BETA"}),": We don't anticipate any ",(0,s.jsx)(t.em,{children:"large breaking changes"})," to the feature/API. It is covered by our integration tests, but it ",(0,s.jsx)(t.em,{children:"might"})," change before the ",(0,s.jsx)(t.code,{children:"1.0.0"})," release."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"EXPERIMENTAL"}),": Try it out and give us feedback! But you might want to wait a release or two before putting it into production."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"0.6.0"})," release notes will include a table of all of our features and their API Stability Level in all four of our SDK's."]})]})}function h(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>o,x:()=>l});var s=n(6540);const i={},r=s.createContext(i);function o(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/d61070d0.9bde1063.js b/assets/js/d61070d0.9bde1063.js
new file mode 100644
index 000000000..6b2b74aed
--- /dev/null
+++ b/assets/js/d61070d0.9bde1063.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[6583],{3535:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>c,frontMatter:()=>o,metadata:()=>a,toc:()=>h});var r=s(4848),i=s(8453);const o={title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},n=void 0,a={permalink:"/blog/littlehorse-0.7-release",source:"@site/blog/2024-01-28-0.7-release.md",title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",date:"2024-01-28T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:4.535,hasTruncateMarker:!0,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.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"},nextItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},h=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"Administrative Dashboard",id:"administrative-dashboard",level:3},{value:"Idempotent Metadata Management",id:"idempotent-metadata-management",level:3},{value:"Child Workflows",id:"child-workflows",level:3},{value:"Enhanced SearchWfRun
",id:"enhanced-searchwfrun",level:3},{value:"What's Next?",id:"whats-next",level:2},{value:"Apache2 Clients",id:"apache2-clients",level:3},{value:"Tutorials",id:"tutorials",level:3},{value:"Approaching 1.0.0
",id:"approaching-100",level:3},{value:"LH Cloud",id:"lh-cloud",level:3}];function d(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,r.jsx)(t.code,{children:"0.7.2"}),"! This is our last release before we cut ",(0,r.jsx)(t.code,{children:"1.0.0"}),", which will be the first stable and production-ready LittleHorse distribution."]}),"\n",(0,r.jsx)(t.h2,{id:"get-started",children:"Get Started"}),"\n",(0,r.jsx)(t.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,r.jsx)(t.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["Visit us on ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises",children:"GitHub"})]}),"\n",(0,r.jsxs)(t.li,{children:["Try our ",(0,r.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install#installation-and-quickstart",children:"quickstarts"})," or watch our founder, Colt, go through them in ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=8Zo_UOStg98&t=6s",children:"Java"}),", ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=oZQc2ISSZsk",children:"Go"}),", or ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=l3TZOjfpzTw",children:"Python"})]}),"\n",(0,r.jsxs)(t.li,{children:["Join our ",(0,r.jsx)(t.a,{href:"https://launchpass.com/littlehorse-community",children:"Slack Community"})," for quick and responsive help!"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Also, LittleHorse Enterprises LLC has released its first out our ",(0,r.jsx)(t.a,{href:"https://littlehorse.io",children:"product-focused website"}),"! If you're still curious and want to learn even more, check out a few of our new in-depth tutorial series on ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/@LittleHorse-ey3vw/featured",children:"our YouTube page"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(t.p,{children:["Release ",(0,r.jsx)(t.code,{children:"0.7"})," introduces many features designed to make your life easier. We plan to write blogs about all of them, so stay tuned!"]}),"\n",(0,r.jsx)(t.h3,{id:"administrative-dashboard",children:"Administrative Dashboard"}),"\n",(0,r.jsxs)(t.p,{children:["The most exciting part of the ",(0,r.jsx)(t.code,{children:"0.7.2"})," release of LittleHorse is the new LH Dashboard, which is an administrative portal into your LittleHorse Cluster. The LH Dashboard lets you check on all of your workflows and tasks and debug everything visually with fine-grained detail. Our quickstarts (see above) have everything you need to get started debugging your workflows with our dashboard."]}),"\n",(0,r.jsxs)(t.p,{children:["The LH Dashboard is in the alpha stage, so we appreciate any bug reports or feature requests. Please file them on ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/issues",children:"our github"}),"!"]}),"\n",(0,r.jsx)(t.h3,{id:"idempotent-metadata-management",children:"Idempotent Metadata Management"}),"\n",(0,r.jsxs)(t.p,{children:["Managing your ",(0,r.jsx)(t.code,{children:"WfSpec"}),"s and ",(0,r.jsx)(t.code,{children:"TaskDef"}),"s just got much easier. Check out our ",(0,r.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/managing-metadata",children:"updated docs"})," for tutorials on how to keep your DevOps team happy and seamlessly integrate LittleHorse into your normal application development lifecycle."]}),"\n",(0,r.jsx)(t.h3,{id:"child-workflows",children:"Child Workflows"}),"\n",(0,r.jsxs)(t.p,{children:["We also added the ability to run a ",(0,r.jsx)(t.code,{children:"WfRun"}),' which is a "child" of another ',(0,r.jsx)(t.code,{children:"WfRun"}),". This allows for some interesting features, most importantly:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["Sharing ",(0,r.jsx)(t.code,{children:"Variable"}),"s between ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s"]}),"\n",(0,r.jsxs)(t.li,{children:["Foreign-key relationships between the child and parent ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s."]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Stay tuned for an upcoming blog about ",(0,r.jsx)(t.em,{children:"why"}),' we added that feature. It was guided by our resident Domain-Driven Design expert, Eduwer Camacaro! Here\'s a hint: this feature makes it possible to use LittleHorse Workflows as a native data store for complex business entities. This is a great way to implement the "Aggregate Pattern."']}),"\n",(0,r.jsxs)(t.h3,{id:"enhanced-searchwfrun",children:["Enhanced ",(0,r.jsx)(t.code,{children:"SearchWfRun"})]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"rpc SearchWfRun"})," request now has a ",(0,r.jsx)(t.code,{children:"repeated VariableMatch variable_filters"})," field on it. This allows you to filter ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s by the value of one or more ",(0,r.jsx)(t.code,{children:"Variable"}),"'s when searching for them, returning only matching ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s. This is super useful when using a LittleHorse ",(0,r.jsx)(t.code,{children:"WfRun"}),' to model a business entity, and you need to do something like "find all orders placed by ',(0,r.jsx)(t.code,{children:"user-id == john"})," and ",(0,r.jsx)(t.code,{children:"status == OUT_FOR_SHIPPING"}),'".']}),"\n",(0,r.jsxs)(t.p,{children:["In the past, this was possible using the ",(0,r.jsx)(t.code,{children:"rpc SearchVariable"})," and then back the ",(0,r.jsx)(t.code,{children:"WfRunId"})," out of the ",(0,r.jsx)(t.code,{children:"VariableId"}),"; however, that method is a little bit clunky. In reality, our users want to find a ",(0,r.jsx)(t.code,{children:"WfRunId"})," matching certain criteria; they're not looking for a ",(0,r.jsx)(t.code,{children:"Variable"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"whats-next",children:"What's Next?"}),"\n",(0,r.jsx)(t.p,{children:"We couldn't be more excited about what is coming next."}),"\n",(0,r.jsx)(t.h3,{id:"apache2-clients",children:"Apache2 Clients"}),"\n",(0,r.jsxs)(t.p,{children:["Some members of the community have expressed concerns about our clients (SDK's + GRPC code) being licensed by the SSPL license. We heard you, and we will update them to the Apache 2.0 License before our ",(0,r.jsx)(t.code,{children:"1.0.0"})," release! The server will remain SSPL."]}),"\n",(0,r.jsx)(t.h3,{id:"tutorials",children:"Tutorials"}),"\n",(0,r.jsxs)(t.p,{children:["One of our team members, Sohini, has been hard at work creating video tutorials which will help you get quickly up to speed on advanced LittleHorse concepts. You can find them here on our ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/@LittleHorse-ey3vw/playlists",children:"YouTube"}),"."]}),"\n",(0,r.jsxs)(t.p,{children:["Additionally, our founder has recorded a series of zoom meetings with himself (yes, you read that right...Colt used zoom to record a tutorial video series) going through quickstarts in all of our three SDK's. You can find them here in ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=8Zo_UOStg98&t=6s",children:"Java"}),", ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=oZQc2ISSZsk",children:"Go"}),", or ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=l3TZOjfpzTw",children:"Python"}),"."]}),"\n",(0,r.jsxs)(t.h3,{id:"approaching-100",children:["Approaching ",(0,r.jsx)(t.code,{children:"1.0.0"})]}),"\n",(0,r.jsxs)(t.p,{children:["What's missing before ",(0,r.jsx)(t.code,{children:"1.0.0"}),"? We have some in-progress features that are already merged to ",(0,r.jsx)(t.code,{children:"master"})," but only partially implemented. If you squint hard enough at our GRPC Api, you might notice that we have support for multi-tenancy and also fine-grained ACL's. They are NOT ready for production use as we need to iron out a few wrinkles, but we will have them ready for ",(0,r.jsx)(t.code,{children:"1.0.0"}),". We also are working on an ",(0,r.jsx)(t.code,{children:"rpc MigrateWfSpec"})," which allows you to migrate a running ",(0,r.jsx)(t.code,{children:"WfRun"})," from an older version of a ",(0,r.jsx)(t.code,{children:"WfSpec"})," to a newer version. This is hard work for us but it will be highly useful for our users."]}),"\n",(0,r.jsxs)(t.p,{children:["Additionally, we are expanding our end-to-end test coverage to try to shake out as many issues as possible ",(0,r.jsx)(t.em,{children:"before"})," our users tell us about them. So far, the rate of new bugs that we've discovered has slowed down considerably, which makes us think we are getting close to the quality we expect from our own product."]}),"\n",(0,r.jsxs)(t.p,{children:["What will change when we release ",(0,r.jsx)(t.code,{children:"1.0.0"}),"? We will be following ",(0,r.jsx)(t.a,{href:"https://semver.org",children:"Semantic Versioning"})," to the letter, which means we will be paying ",(0,r.jsx)(t.em,{children:"super close attention"})," to any breaking changes to our API. If we want our users to use us for mission critical workloads, we need to take stability seriously\u2014both in terms of performance and API compatibility."]}),"\n",(0,r.jsx)(t.p,{children:"We will also likely have three minor releases per year, with 12 months of patch support for each minor release. This release schedule is copied from Apache Kafka."}),"\n",(0,r.jsx)(t.h3,{id:"lh-cloud",children:"LH Cloud"}),"\n",(0,r.jsxs)(t.p,{children:["Lastly, stay tuned for LittleHorse Cloud! Early access is open. If you would like to sign up for early access to LH Cloud, visit ",(0,r.jsx)(t.a,{href:"https://www.littlehorse.io/lh-cloud",children:"our website"})," or contact ",(0,r.jsx)(t.code,{children:"sales@littlehorse.io"}),"."]})]})}function c(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>n,x:()=>a});var r=s(6540);const i={},o=r.createContext(i);function n(e){const t=r.useContext(o);return r.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(i):e.components||i:n(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/d61070d0.c32deae4.js b/assets/js/d61070d0.c32deae4.js
deleted file mode 100644
index a8fe6fb98..000000000
--- a/assets/js/d61070d0.c32deae4.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[6583],{3535:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>d,frontMatter:()=>o,metadata:()=>a,toc:()=>h});var r=s(4848),i=s(8453);const o={title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},n=void 0,a={permalink:"/blog/littlehorse-0.7-release",source:"@site/blog/2024-01-28-0.7-release.md",title:"Releasing 0.7",description:"Approaching a stable `1.0.0` release.",date:"2024-01-28T00:00:00.000Z",tags:[{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."},{inline:!1,label:"LittleHorse Releases",permalink:"/blog/tags/release/",description:"Release blogs for LittleHorse Orchestrator."}],readingTime:4.535,hasTruncateMarker:!0,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.7",description:"Approaching a stable `1.0.0` release.",slug:"littlehorse-0.7-release",authors:["lh_council"],tags:["littlehorse","release"],image:"https://avatars.githubusercontent.com/u/140006313?s=400&u=7bf4c91d92dfe590ac71bb6b4821e1a81aa5b712&v=4",hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Releasing 0.8",permalink:"/blog/littlehorse-0.8-release"},nextItem:{title:"Releasing 0.5.0",permalink:"/blog/littlehorse-0.5.0-release"}},l={authorsImageUrls:[void 0]},h=[{value:"Get Started",id:"get-started",level:2},{value:"New Features",id:"new-features",level:2},{value:"Administrative Dashboard",id:"administrative-dashboard",level:3},{value:"Idempotent Metadata Management",id:"idempotent-metadata-management",level:3},{value:"Child Workflows",id:"child-workflows",level:3},{value:"Enhanced SearchWfRun
",id:"enhanced-searchwfrun",level:3},{value:"What's Next?",id:"whats-next",level:2},{value:"Apache2 Clients",id:"apache2-clients",level:3},{value:"Tutorials",id:"tutorials",level:3},{value:"Approaching 1.0.0
",id:"approaching-100",level:3},{value:"LH Cloud",id:"lh-cloud",level:3}];function c(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(t.p,{children:["We are excited to announce the release of ",(0,r.jsx)(t.code,{children:"0.7.2"}),"! This is our last release before we cut ",(0,r.jsx)(t.code,{children:"1.0.0"}),", which will be the first stable and production-ready LittleHorse distribution."]}),"\n",(0,r.jsx)(t.h2,{id:"get-started",children:"Get Started"}),"\n",(0,r.jsx)(t.p,{children:"LittleHorse is free for production use according to the Server-Side Public License!"}),"\n",(0,r.jsx)(t.p,{children:"To get started with LittleHorse OSS, you can:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["Visit us on ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises",children:"GitHub"})]}),"\n",(0,r.jsxs)(t.li,{children:["Try our ",(0,r.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/install#installation-and-quickstart",children:"quickstarts"})," or watch our founder, Colt, go through them in ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=8Zo_UOStg98&t=6s",children:"Java"}),", ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=oZQc2ISSZsk",children:"Go"}),", or ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=l3TZOjfpzTw",children:"Python"})]}),"\n",(0,r.jsxs)(t.li,{children:["Join our ",(0,r.jsx)(t.a,{href:"https://launchpass.com/littlehorse-community",children:"Slack Community"})," for quick and responsive help!"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Also, LittleHorse Enterprises LLC has released its first out our ",(0,r.jsx)(t.a,{href:"https://littlehorse.io",children:"product-focused website"}),"! If you're still curious and want to learn even more, check out a few of our new in-depth tutorial series on ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/@LittleHorse-ey3vw/featured",children:"our YouTube page"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"new-features",children:"New Features"}),"\n",(0,r.jsxs)(t.p,{children:["Release ",(0,r.jsx)(t.code,{children:"0.7"})," introduces many features designed to make your life easier. We plan to write blogs about all of them, so stay tuned!"]}),"\n",(0,r.jsx)(t.h3,{id:"administrative-dashboard",children:"Administrative Dashboard"}),"\n",(0,r.jsxs)(t.p,{children:["The most exciting part of the ",(0,r.jsx)(t.code,{children:"0.7.2"})," release of LittleHorse is the new LH Dashboard, which is an administrative portal into your LittleHorse Cluster. The LH Dashboard lets you check on all of your workflows and tasks and debug everything visually with fine-grained detail. Our quickstarts (see above) have everything you need to get started debugging your workflows with our dashboard."]}),"\n",(0,r.jsxs)(t.p,{children:["The LH Dashboard is in the alpha stage, so we appreciate any bug reports or feature requests. Please file them on ",(0,r.jsx)(t.a,{href:"https://github.com/littlehorse-enterprises/littlehorse/issues",children:"our github"}),"!"]}),"\n",(0,r.jsx)(t.h3,{id:"idempotent-metadata-management",children:"Idempotent Metadata Management"}),"\n",(0,r.jsxs)(t.p,{children:["Managing your ",(0,r.jsx)(t.code,{children:"WfSpec"}),"s and ",(0,r.jsx)(t.code,{children:"TaskDef"}),"s just got much easier. Check out our ",(0,r.jsx)(t.a,{href:"https://littlehorse.dev/docs/developer-guide/grpc/managing-metadata",children:"updated docs"})," for tutorials on how to keep your DevOps team happy and seamlessly integrate LittleHorse into your normal application development lifecycle."]}),"\n",(0,r.jsx)(t.h3,{id:"child-workflows",children:"Child Workflows"}),"\n",(0,r.jsxs)(t.p,{children:["We also added the ability to run a ",(0,r.jsx)(t.code,{children:"WfRun"}),' which is a "child" of another ',(0,r.jsx)(t.code,{children:"WfRun"}),". This allows for some interesting features, most importantly:"]}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["Sharing ",(0,r.jsx)(t.code,{children:"Variable"}),"s between ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s"]}),"\n",(0,r.jsxs)(t.li,{children:["Foreign-key relationships between the child and parent ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s."]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Stay tuned for an upcoming blog about ",(0,r.jsx)(t.em,{children:"why"}),' we added that feature. It was guided by our resident Domain-Driven Design expert, Eduwer Camacaro! Here\'s a hint: this feature makes it possible to use LittleHorse Workflows as a native data store for complex business entities. This is a great way to implement the "Aggregate Pattern."']}),"\n",(0,r.jsxs)(t.h3,{id:"enhanced-searchwfrun",children:["Enhanced ",(0,r.jsx)(t.code,{children:"SearchWfRun"})]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"rpc SearchWfRun"})," request now has a ",(0,r.jsx)(t.code,{children:"repeated VariableMatch variable_filters"})," field on it. This allows you to filter ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s by the value of one or more ",(0,r.jsx)(t.code,{children:"Variable"}),"'s when searching for them, returning only matching ",(0,r.jsx)(t.code,{children:"WfRun"}),"'s. This is super useful when using a LittleHorse ",(0,r.jsx)(t.code,{children:"WfRun"}),' to model a business entity, and you need to do something like "find all orders placed by ',(0,r.jsx)(t.code,{children:"user-id == john"})," and ",(0,r.jsx)(t.code,{children:"status == OUT_FOR_SHIPPING"}),'".']}),"\n",(0,r.jsxs)(t.p,{children:["In the past, this was possible using the ",(0,r.jsx)(t.code,{children:"rpc SearchVariable"})," and then back the ",(0,r.jsx)(t.code,{children:"WfRunId"})," out of the ",(0,r.jsx)(t.code,{children:"VariableId"}),"; however, that method is a little bit clunky. In reality, our users want to find a ",(0,r.jsx)(t.code,{children:"WfRunId"})," matching certain criteria; they're not looking for a ",(0,r.jsx)(t.code,{children:"Variable"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"whats-next",children:"What's Next?"}),"\n",(0,r.jsx)(t.p,{children:"We couldn't be more excited about what is coming next."}),"\n",(0,r.jsx)(t.h3,{id:"apache2-clients",children:"Apache2 Clients"}),"\n",(0,r.jsxs)(t.p,{children:["Some members of the community have expressed concerns about our clients (SDK's + GRPC code) being licensed by the SSPL license. We heard you, and we will update them to the Apache 2.0 License before our ",(0,r.jsx)(t.code,{children:"1.0.0"})," release! The server will remain SSPL."]}),"\n",(0,r.jsx)(t.h3,{id:"tutorials",children:"Tutorials"}),"\n",(0,r.jsxs)(t.p,{children:["One of our team members, Sohini, has been hard at work creating video tutorials which will help you get quickly up to speed on advanced LittleHorse concepts. You can find them here on our ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/@LittleHorse-ey3vw/playlists",children:"YouTube"}),"."]}),"\n",(0,r.jsxs)(t.p,{children:["Additionally, our founder has recorded a series of zoom meetings with himself (yes, you read that right...Colt used zoom to record a tutorial video series) going through quickstarts in all of our three SDK's. You can find them here in ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=8Zo_UOStg98&t=6s",children:"Java"}),", ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=oZQc2ISSZsk",children:"Go"}),", or ",(0,r.jsx)(t.a,{href:"https://www.youtube.com/watch?v=l3TZOjfpzTw",children:"Python"}),"."]}),"\n",(0,r.jsxs)(t.h3,{id:"approaching-100",children:["Approaching ",(0,r.jsx)(t.code,{children:"1.0.0"})]}),"\n",(0,r.jsxs)(t.p,{children:["What's missing before ",(0,r.jsx)(t.code,{children:"1.0.0"}),"? We have some in-progress features that are already merged to ",(0,r.jsx)(t.code,{children:"master"})," but only partially implemented. If you squint hard enough at our GRPC Api, you might notice that we have support for multi-tenancy and also fine-grained ACL's. They are NOT ready for production use as we need to iron out a few wrinkles, but we will have them ready for ",(0,r.jsx)(t.code,{children:"1.0.0"}),". We also are working on an ",(0,r.jsx)(t.code,{children:"rpc MigrateWfSpec"})," which allows you to migrate a running ",(0,r.jsx)(t.code,{children:"WfRun"})," from an older version of a ",(0,r.jsx)(t.code,{children:"WfSpec"})," to a newer version. This is hard work for us but it will be highly useful for our users."]}),"\n",(0,r.jsxs)(t.p,{children:["Additionally, we are expanding our end-to-end test coverage to try to shake out as many issues as possible ",(0,r.jsx)(t.em,{children:"before"})," our users tell us about them. So far, the rate of new bugs that we've discovered has slowed down considerably, which makes us think we are getting close to the quality we expect from our own product."]}),"\n",(0,r.jsxs)(t.p,{children:["What will change when we release ",(0,r.jsx)(t.code,{children:"1.0.0"}),"? We will be following ",(0,r.jsx)(t.a,{href:"https://semver.org",children:"Semantic Versioning"})," to the letter, which means we will be paying ",(0,r.jsx)(t.em,{children:"super close attention"})," to any breaking changes to our API. If we want our users to use us for mission critical workloads, we need to take stability seriously\u2014both in terms of performance and API compatibility."]}),"\n",(0,r.jsx)(t.p,{children:"We will also likely have three minor releases per year, with 12 months of patch support for each minor release. This release schedule is copied from Apache Kafka."}),"\n",(0,r.jsx)(t.h3,{id:"lh-cloud",children:"LH Cloud"}),"\n",(0,r.jsxs)(t.p,{children:["Lastly, stay tuned for LittleHorse Cloud! Early access is open. If you would like to sign up for early access to LH Cloud, visit ",(0,r.jsx)(t.a,{href:"https://www.littlehorse.io/lh-cloud",children:"our website"})," or contact ",(0,r.jsx)(t.code,{children:"sales@littlehorse.io"}),"."]})]})}function d(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},8453:(e,t,s)=>{s.d(t,{R:()=>n,x:()=>a});var r=s(6540);const i={},o=r.createContext(i);function n(e){const t=r.useContext(o);return r.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(i):e.components||i:n(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/df0890b4.4efc3585.js b/assets/js/df0890b4.a0b8ea9e.js
similarity index 77%
rename from assets/js/df0890b4.4efc3585.js
rename to assets/js/df0890b4.a0b8ea9e.js
index c609b21d0..631859c84 100644
--- a/assets/js/df0890b4.4efc3585.js
+++ b/assets/js/df0890b4.a0b8ea9e.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":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
+"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":6,"unlisted":false},"listMetadata":{"permalink":"/blog/tags/analysis/","page":1,"postsPerPage":20,"totalPages":1,"totalCount":6,"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.70c7082b.js b/assets/js/e3618154.70c7082b.js
deleted file mode 100644
index b4f413c09..000000000
--- a/assets/js/e3618154.70c7082b.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:()=>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/e3618154.9b13d064.js b/assets/js/e3618154.9b13d064.js
new file mode 100644
index 000000000..dc3f99940
--- /dev/null
+++ b/assets/js/e3618154.9b13d064.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","microservice-and-workflow"]},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."},{inline:!1,label:"Microservices and Workflow",permalink:"/blog/tags/microservice-and-workflow/",description:"A 3-part blog series on the challenges inherent with the microservice architecture, and how Workflow Engines can mitigate those difficulties."}],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","microservice-and-workflow"]},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.2e85f147.js b/assets/js/ef8b811a.a4c3f802.js
similarity index 97%
rename from assets/js/ef8b811a.2e85f147.js
rename to assets/js/ef8b811a.a4c3f802.js
index 5fc61a22f..872febbe0 100644
--- a/assets/js/ef8b811a.2e85f147.js
+++ b/assets/js/ef8b811a.a4c3f802.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},{"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
+"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":5},{"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/f21a8f01.939e0c35.js b/assets/js/f21a8f01.939e0c35.js
new file mode 100644
index 000000000..883652313
--- /dev/null
+++ b/assets/js/f21a8f01.939e0c35.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunklh_site=self.webpackChunklh_site||[]).push([[2558],{6720:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var s=n(4848),a=n(8453);const i={slug:"transactional-outbox",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},r="Integration Patterns: Transactional Outbox",o={permalink:"/blog/transactional-outbox",source:"@site/blog/2024-09-30-transactional-outbox.md",title:"Integration Patterns: Transactional Outbox",description:"Like the Saga Pattern, the Transactional Outbox pattern is tool for defending against data loss in your applications. In this blog we cover how it works and how to do it easier using LittleHorse.",date:"2024-09-30T00: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:"Integration Patterns",permalink:"/blog/tags/integration-patterns/",description:"A 5-part blog series on Integration Patterns that are useful for event-driven systems."},{inline:!1,label:"LittleHorse Orchestrator",permalink:"/blog/tags/littlehorse/",description:"Information about the LittleHorse Orchestrator."}],readingTime:5.74,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:"transactional-outbox",authors:["coltmcnealy"],tags:["analysis","integration-patterns","littlehorse"]},unlisted:!1,nextItem:{title:"Integration Patterns: Saga Transactions",permalink:"/blog/saga-pattern"}},l={authorsImageUrls:[void 0]},c=[{value:"The Transactional Outbox Pattern",id:"the-transactional-outbox-pattern",level:2},{value:"Case Study: Customer Sign-Up",id:"case-study-customer-sign-up",level:2},{value:"Using a Transactional Outbox",id:"using-a-transactional-outbox",level:3},{value:"Using LittleHorse",id:"using-littlehorse",level:3},{value:"Wrapping Up",id:"wrapping-up",level:2},{value:"Additional Use Cases",id:"additional-use-cases",level:3},{value:"Alternative: Log-First Architecture",id:"alternative-log-first-architecture",level:3},{value:"Get Involved!",id:"get-involved",level:3}];function h(e){const t={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,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["Like the ",(0,s.jsx)(t.a,{href:"/blog/saga-pattern",children:"Saga Pattern"}),", the Transactional Outbox pattern is tool for defending against data loss in your applications. In this blog we cover how it works and how to do it ",(0,s.jsx)(t.em,{children:"easier"})," using LittleHorse."]}),"\n",(0,s.jsxs)(t.admonition,{type:"info",children:[(0,s.jsx)(t.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,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:(0,s.jsx)(t.a,{href:"/blog/saga-pattern",children:"Saga Transactions"})}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"[This Post]"})," The Transactional Outbox Pattern"]}),"\n",(0,s.jsx)(t.li,{children:"[Coming soon] Queuing and Backpressure"}),"\n",(0,s.jsx)(t.li,{children:"[Coming soon] Retries and Dead-Letter Queues"}),"\n",(0,s.jsx)(t.li,{children:"[Coming soon] Callbacks and External Events"}),"\n"]})]}),"\n",(0,s.jsx)(t.h2,{id:"the-transactional-outbox-pattern",children:"The Transactional Outbox Pattern"}),"\n",(0,s.jsxs)(t.p,{children:["At the technical level, the ",(0,s.jsx)(t.a,{href:"https://microservices.io/patterns/data/transactional-outbox.html",children:"Transactional Outbox Pattern"})," allows you to atomically:"]}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"Update a database, and"}),"\n",(0,s.jsx)(t.li,{children:"Publish a record to a streaming log or message queue (such as Apache Kafka)."}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.a,{href:"/blog/saga-pattern",children:"Saga Pattern"})," allows you to make a multi-step business process atomic. However, you can think of the Transactional Outbox pattern as a way to ensure that a process doesn't get dropped halfway through."]}),"\n",(0,s.jsxs)(t.admonition,{type:"tip",children:[(0,s.jsxs)(t.p,{children:["The Transactional Outbox Pattern is often useful ",(0,s.jsx)(t.em,{children:"within"})," a Saga transaction."]}),(0,s.jsx)(t.p,{children:"However, as we'll see later on in this article, LittleHorse removes the need to worry about such difficult technical details."})]}),"\n",(0,s.jsx)(t.h2,{id:"case-study-customer-sign-up",children:"Case Study: Customer Sign-Up"}),"\n",(0,s.jsxs)(t.p,{children:["As an example, let's consider the following Spring Boot REST endpoint (",(0,s.jsx)(t.code,{children:"POST /user"}),"), which must:"]}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"Create a customer account in a database."}),"\n",(0,s.jsx)(t.li,{children:"Send a message on a queue which results in a series of account setup actions, including a welcome email being sent to the customer."}),"\n"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-java",children:'@PostMapping("/user")\npublic ResponseEntityYour 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