<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Josh Wulf</title>
    <description>The latest articles on Forem by Josh Wulf (@jwulf).</description>
    <link>https://forem.com/jwulf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F28579%2F2dbc4c0b-ffc4-4c18-9214-88bb84670a03.jpg</url>
      <title>Forem: Josh Wulf</title>
      <link>https://forem.com/jwulf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jwulf"/>
    <language>en</language>
    <item>
      <title>Creating an Extraordinary Contributor Experience</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Tue, 22 Sep 2020 08:38:01 +0000</pubDate>
      <link>https://forem.com/jwulf/creating-an-extraordinary-contributor-experience-505</link>
      <guid>https://forem.com/jwulf/creating-an-extraordinary-contributor-experience-505</guid>
      <description>&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktoberfest&lt;/a&gt; is coming! And &lt;a href="https://camunda.com/hacktoberfest2020/"&gt;Camunda is participating&lt;/a&gt;, with the opportunity to get a sweet limited-edition Camunda Hacktoberfest t-shirt by making just two pull requests to any of our open source projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MK1R6HAi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hg2zdc5xrujr24i030bf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MK1R6HAi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hg2zdc5xrujr24i030bf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've also put together a guide for open source repository maintainers, to empower you to create an extraordinary contributor experience.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating an Extraordinary Contributor Experience
&lt;/h1&gt;

&lt;p&gt;This document is for open source project maintainers who want to increase community contributions to their project. &lt;/p&gt;

&lt;p&gt;We'll look at the Contributor Experience and how you can create an Extraordinary Contributor Experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  So You're a Maintainer
&lt;/h2&gt;

&lt;p&gt;You're an open source project maintainer. &lt;/p&gt;

&lt;p&gt;You're probably also a contributor to the project - maybe the only one. &lt;/p&gt;

&lt;p&gt;Wouldn't it be great to have other people making contributions to the codebase?&lt;/p&gt;

&lt;p&gt;Or maybe you're part of a team that works professionally on a code base. Maybe you have users asking for features and bug fixes. Wouldn't it be great if the contributions grew at the same rate as the requests?&lt;/p&gt;

&lt;p&gt;Contributors not only provide value to the project by testing, opening issues, and contributing code, they also become advocates for the project in the wider community. And why not? It's their project too, now. They'll tweet out when their code goes into a release, and add it to their CV.&lt;/p&gt;

&lt;p&gt;Maybe you've been an external contributor to an open source project prior to become a project maintainer - maybe you haven't.&lt;/p&gt;

&lt;p&gt;Either way, you probably haven't thought about creating a contributor experience, systematically. &lt;/p&gt;

&lt;p&gt;That is what we are going to look at here. &lt;/p&gt;

&lt;p&gt;Why? Because repeat contributions, like repeat customers, come from an extraordinary experience. Not everyone will repeat - but if your contributor experience is extraordinary, you will increase the amount of contributions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Contributes
&lt;/h2&gt;

&lt;p&gt;Let's first consider &lt;em&gt;who&lt;/em&gt; contributes.&lt;/p&gt;

&lt;p&gt;We will create a few archetypes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First-timer&lt;/strong&gt;: The first-timer is someone who has never contributed to any open source project before. This is all new to them. They don't know how to "open a pull request", they are not sure if their contribution will be good enough to be accepted, and if they do take the plunge, how this pull request goes will determine how their entire open source contribution career goes. No pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enthusiastic Amateur&lt;/strong&gt;: The Enthusiastic Amateur hacks code together and submits it fast and furiously. They have enthusiasm, but their code is not always up to scratch, and may come in without tests, or with spelling errors. They may lose their enthusiasm if required to make too many changes to get their pull request accepted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seasoned Pro&lt;/strong&gt;: The Seasoned Pro is not only a solid developer who knows not only how to write good code, but also how open source works. They've done a bunch of PRs to various open-source projects, so they know what to expect. This person compares their experience with your project with their experience with other projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Characteristics of External Contributors
&lt;/h2&gt;

&lt;p&gt;People are usually busy. They might be using your project for their day job. You are focused on the project itself - it's the main event. For a lot of potential contributors, however, your project probably is one piece in a complex stack of technology that they are wrestling with. It's an extra on the show. &lt;/p&gt;

&lt;p&gt;Any contribution that people make to the project is a generous contribution of their valuable time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create an Extraordinary Contributor Experience
&lt;/h2&gt;

&lt;p&gt;We're going to cover a number of specific tactical actions that you can take, strategically.&lt;/p&gt;

&lt;p&gt;However, there are two things that will make everything work. These are orientations, rather than actions. If you just get these two things, everything will work. If you do everything else and neglect these, your results will be variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always Deal with The Person Behind the Pull Request
&lt;/h3&gt;

&lt;p&gt;Behind the bug report, feature request, or pull request is a person.&lt;/p&gt;

&lt;p&gt;Always deal with the person. Deal with the person about the pull request, rather than just dealing with the pull request.&lt;/p&gt;

&lt;p&gt;It's obvious and easy to think that we are building software. Something that is just as easy to forget is that in open source we are &lt;em&gt;building software together&lt;/em&gt;. So while we are building software, we are also building an experience of building software.&lt;/p&gt;

&lt;p&gt;Before joining Camunda's DevRel team, Josh was an external contributor to the Zeebe project. While evaluating Zeebe against other open source workflow engines, he opened various issues and pull requests.&lt;/p&gt;

&lt;p&gt;The pull requests to other projects languished, but the Zeebe ones got feedback, and got merged. He says that his experience was that his contribution was acknowledged and appreciated. That gave him confidence that if his company at that time went with Zeebe, they would be able to contribute fixes to the engine for their use case, and they would get merged.&lt;/p&gt;

&lt;p&gt;Think about the person behind the pull request. Put yourself in their shoes. Think back to the best experience that you've had when contributing to a project. Maybe it was your first pull request, or another time. You probably experienced being acknowledged and appreciated, as a person, as well as respected professionally for your contribution.&lt;/p&gt;

&lt;p&gt;On the flipside, a prominent open source company let one of its best engineers go because of their interactions with the community. This was an A-grade rockstar contributor - legendary in the open source world for their work on the Linux kernel. Unfortunately, they were also legendary for their interactions with contributors - and not in a good way. A typical PR response went along the lines of "&lt;em&gt;your code is stupid, and so are you&lt;/em&gt;". &lt;/p&gt;

&lt;p&gt;Don't be that guy.&lt;/p&gt;

&lt;p&gt;If you treat your contributors well, they will come back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Have the Contributor Win
&lt;/h3&gt;

&lt;p&gt;People contribute for different reasons. &lt;/p&gt;

&lt;p&gt;Some people are fixing a problem or implementing a feature for their use case, and they want it merged upstream so that they don't have to maintain a separate fork.&lt;/p&gt;

&lt;p&gt;Some people are out to learn, and to build a reputation for themselves that is portable and visible.&lt;/p&gt;

&lt;p&gt;Some people are just good open source citizens. Fixing things they come across is like picking up a piece of litter on the street.&lt;/p&gt;

&lt;p&gt;Some people are evaluating the project for use, and want to get a sense of how amenable and responsive it is to Pull Requests.&lt;/p&gt;

&lt;p&gt;For all of these cases, if you take the stand that you are &lt;em&gt;the champion of this contributor&lt;/em&gt; and it is your role to "have them win", then you will find the way to empower them, wherever they are coming from.&lt;/p&gt;

&lt;p&gt;A contributor may have time to work on further changes to a Pull Request, such as documentation or tests - and they may not. &lt;/p&gt;

&lt;p&gt;Sometimes, it is a "drive-by" contribution. Someone fixing something in a dependency of their project and moving on with their day job. They may not have time to do additional changes to make it just right.&lt;/p&gt;

&lt;p&gt;If you deal with the person behind the Pull Request, and see yourself as their champion, then you will be sensitive to their ability and appetite for extended collaboration. &lt;/p&gt;

&lt;p&gt;Maybe they want to learn, and are eager to make further changes. Maybe not. But if your goal is to "have them win" - to get their contribution merged and their name in lights, then you will figure that out with them.&lt;/p&gt;

&lt;p&gt;You have to balance something here: if a pull request comes in that requires a significant amount of work to get over the line, you may have a problem.&lt;/p&gt;

&lt;p&gt;The contributor may not have had in mind that getting the PR over the line would turn into another full-time job for them. And you already have other things that you are working on, so fixing a half-done pull request is putting more work on your plate.&lt;/p&gt;

&lt;p&gt;One of the things to factor in - as you work out whether it is worth the effort for you to get it over the line - is the longer-term investment in community capital. &lt;/p&gt;

&lt;p&gt;Merged pull requests beget more pull requests. And one of the metrics used to evaluate project health - for people who are considering betting on your project by basing their project on it - is how many open pull requests there are, and how long they have been open.&lt;/p&gt;

&lt;p&gt;If you can't see your way to get it done, and the contributor says that they can't get it done, then you have a couple of options.&lt;/p&gt;

&lt;p&gt;You can close the pull request, and they can reopen it later, when they have time to work on it. Or you can pull it into a branch to work on later. If you do the latter, they will get props when it does get merged to the main code base.&lt;/p&gt;

&lt;p&gt;Either way, acknowledge them and thank them.&lt;/p&gt;

&lt;p&gt;If you do invest the time, either to work with them, or to get it over the line for them, know that you are making a long-term investment in the community capital of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare for guests
&lt;/h3&gt;

&lt;p&gt;Here we get into some tactical steps that you can take to create an extraordinary contributor experience.&lt;/p&gt;

&lt;p&gt;Before anyone comes to contribute, you can prepare the project for them.&lt;/p&gt;

&lt;p&gt;Think of the project repository like a store, with a customer experience design. Users are one set of customers, and contributors are another. &lt;/p&gt;

&lt;p&gt;A good example of design is the Apple Store layout. Everything is clearly marked, and discoverable. When people come in to an Apple Store, they are not wandering around a labyrinth, left to puzzle things out for themselves.&lt;/p&gt;

&lt;p&gt;Add templates to the repo for Pull Requests and Issues. This gives users guidance when they open a Here is a great collection of templates: &lt;a href="https://github.com/stevemao/github-issue-templates"&gt;https://github.com/stevemao/github-issue-templates&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put the sign out in front
&lt;/h3&gt;

&lt;p&gt;Every business needs to let people know that they exist, and that they are open for business. Let people know that you accept contributions. Put it at the top of the README.md file, so that it's one of the first things that they read, with a link to the contribution instructions.&lt;/p&gt;

&lt;p&gt;It's one thing to include a CONTRIBUTING.md file in your repo, it's another thing to "&lt;em&gt;stand out on the sidewalk and hand out fliers&lt;/em&gt;". "&lt;em&gt;Putting the sign out in front&lt;/em&gt;" doesn't mean the front of the project - it means &lt;em&gt;in front of the contributor&lt;/em&gt;. People will land in different places in your project from a search engine, or by focusing in on a specific area of interest. If you catch them at the right moment, in the right place, with the right message, a brief moment of inspiration can convert into a contribution.&lt;/p&gt;

&lt;p&gt;Everywhere that represents a touch point or an area that the community can contribute to, weave it into the narrative.&lt;/p&gt;

&lt;p&gt;Put it in the Getting Started instructions: "&lt;em&gt;If you find something wrong or missing in here, feel free to submit a pull request.&lt;/em&gt;" (with a link to the documentation contribution section of your contribution instructions).&lt;/p&gt;

&lt;p&gt;Put it in the API documentation. Put it in the issue template. Put it everywhere (but don't overdo it). &lt;/p&gt;

&lt;p&gt;You want to "&lt;em&gt;walk the floor&lt;/em&gt;". Start with a blank mind, and approach your project as a new user. Go to your repository, your documentation, and your package / image hosting page, and walk through them as a new user. &lt;/p&gt;

&lt;p&gt;Is it obvious that I can contribute to this project? Is it obvious how I do that, or how I find out how to do that?&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it Easy
&lt;/h3&gt;

&lt;p&gt;The lower the barrier to entry, the more people will cross over it.&lt;/p&gt;

&lt;p&gt;If you put links in your documentation so that someone can see something wrong or missing, click a link, correct it and submit a Pull Request, people are more likely to do it.&lt;/p&gt;

&lt;p&gt;If you make it a single click from your README to report a bug, with a template that they can fill out, people are more likely to do it. This also makes your job easier, because you are not constantly going back to ask users for the details you need to triage and assess their issues / contributions. &lt;/p&gt;

&lt;p&gt;If you want people to search existing bug reports first, then give them a link to the existing issues filter, with instructions to search, followed by a link to open a new issue.&lt;/p&gt;

&lt;p&gt;If you want people to contribute code, you need to document how to develop the project. This means installing all prerequisites and building the project. The more of this that you can script, the easier you make it. Potential contributors can burn all of their inspiration trying to get the project to build, before they even think about writing code.&lt;/p&gt;

&lt;p&gt;Again, "&lt;em&gt;walk the floor&lt;/em&gt;". Starting with a blank mind, approach the project as a new user. How easy is it to check out and build? Can you make it easier?&lt;/p&gt;

&lt;p&gt;This can be challenging, because your computer is configured to build the project, and may have dependencies or tools that are required, but not covered by your instructions. Again, this is a perfect opportunity to weave in opportunities for people to contribute. You can make it explicit - "&lt;em&gt;if you find something missing in these instructions, feel free to submit a patch&lt;/em&gt;".&lt;/p&gt;

&lt;h3&gt;
  
  
  Acknowledgement
&lt;/h3&gt;

&lt;p&gt;People contribute to open source for many reasons, but something that every person thrives on is acknowledgement. There is a saying "&lt;em&gt;what gets recognised gets repeated&lt;/em&gt;". &lt;/p&gt;

&lt;p&gt;Acknowledging people for their contribution goes a long way.&lt;/p&gt;

&lt;p&gt;One thing you can do is label any Pull Request coming from a remote fork with a label saying "Thank you".&lt;/p&gt;

&lt;p&gt;And you can add a comment on the pull request,  leaving the person behind the pull request acknowledged and appreciated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeliness
&lt;/h3&gt;

&lt;p&gt;People are busy - including you! &lt;/p&gt;

&lt;p&gt;Remember, though, that we are not just building software here - in open source, we are building &lt;em&gt;a community that builds software&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Just like you attend to infrastructure or production outages, or customer issues, to create an extraordinary contributor experience, you'll need to attend to contributor requests.&lt;/p&gt;

&lt;p&gt;Contributors may have a limited attention span or period of inspiration. It is important to acknowledge their contributions as soon as possible, and then to let them know what time frame they can expect to get a response within.&lt;/p&gt;

&lt;p&gt;When evaluating the Zeebe project as an external contributor, Josh submitted a pull request to fix a bug on a Friday. When he got back to work on Tuesday after a long weekend holiday in Australia, the pull request had been merged and released.&lt;/p&gt;

&lt;p&gt;That went a long way to building confidence in the project, and that company decided to base their project on Zeebe.&lt;/p&gt;

&lt;p&gt;You can't always turn things around that fast, but you can acknowledge receipt and set an expectation. Automation is your friend in this case. You can create a "Contributor SLA" and create automation to trigger reminders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You can get code contributions through Hacktoberfest, but if you take care of the person who makes the contribution, you can get a contributor. And &lt;em&gt;that&lt;/em&gt; is way more valuable.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A Concierge workflow for Hacktoberfest using the Zeebe GitHub Action</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Mon, 24 Aug 2020 10:08:58 +0000</pubDate>
      <link>https://forem.com/jwulf/a-concierge-workflow-for-hacktoberfest-using-the-zeebe-github-action-218</link>
      <guid>https://forem.com/jwulf/a-concierge-workflow-for-hacktoberfest-using-the-zeebe-github-action-218</guid>
      <description>&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DsF5B2X8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b4kt0h64mnfcthpj6bdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DsF5B2X8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b4kt0h64mnfcthpj6bdz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a workflow that uses the &lt;a href="https://github.com/jwulf/zeebe-action"&gt;Zeebe GitHub Action&lt;/a&gt; to enable a "community concierge" to support open source maintainers in providing an extraordinary contributor experience during Hacktoberfest.&lt;/p&gt;

&lt;p&gt;You have one repository that runs a GitHub workflow on a schedule to send notification emails to the concierge email address, and a couple of yaml GitHub workflows that you add to each of the project repos participating in Hacktoberfest.&lt;/p&gt;

&lt;p&gt;You can modify the workflow, but the example one I've created notifies the concierge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When an external PR is opened.&lt;/li&gt;
&lt;li&gt;24 hours later, to make sure that the contribution has been acknowledged by the maintainer.&lt;/li&gt;
&lt;li&gt;Every two days until the PR is closed, to remind the concierge to check that it is moving forward.&lt;/li&gt;
&lt;li&gt;When an external PR is closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Maintainer Must-Haves&lt;/p&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jwulf/zeebe-pr-workflow"&gt;Project with instructions on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;At Camunda, the DevRel team are using this automation to support the maintainers of our various open source projects in providing an extraordinary contributor experience during Hacktoberfest.&lt;/p&gt;

</description>
      <category>actionshackathon</category>
      <category>github</category>
      <category>opensource</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>Refactoring the Zeebe Node gRPC State Machine for Camunda Cloud: Part One</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Sat, 29 Feb 2020 10:32:51 +0000</pubDate>
      <link>https://forem.com/jwulf/refactoring-the-zeebe-node-grpc-state-machine-for-camunda-cloud-part-one-41fp</link>
      <guid>https://forem.com/jwulf/refactoring-the-zeebe-node-grpc-state-machine-for-camunda-cloud-part-one-41fp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Llj4WjEc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AQde5aUyiYKzPCA4_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Llj4WjEc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AQde5aUyiYKzPCA4_.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://joshwulf.com/blog/2020/02/camunda-cloud-connection/"&gt;&lt;em&gt;https://joshwulf.com/blog/2020/02/camunda-cloud-connection&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Preparing for the launch of &lt;a href="https://camunda.io/"&gt;Camunda Cloud&lt;/a&gt;, I turned on continuous integration testing for the &lt;a href="https://www.npmjs.com/package/zeebe-node"&gt;Zeebe Node.js client&lt;/a&gt; against a running instance of a Camunda Cloud Zeebe cluster.&lt;/p&gt;

&lt;p&gt;I was immediately forced to deal with something I’d been conveniently ignoring:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camunda Cloud gRPC connections &lt;em&gt;always&lt;/em&gt; report failure initially, before eventually succeeding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is because the connection to Camunda Cloud is via a TLS-enabled Nginx reverse proxy with OAuth authentication. This causes the current gRPC client state machine to emit intermediate connection failure events before emitting an eventual “READY” state.&lt;/p&gt;

&lt;p&gt;This “connection characteristic” differs from the behaviour of the non-proxied connection over a Docker network (&lt;em&gt;the current CI test environment in CircleCI&lt;/em&gt;) or against a non-proxied broker on a remote machine (&lt;em&gt;my personal production setups not using Camunda Cloud&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;How that looks for a user:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z3INipeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AP41rnd1Sw3Eewokc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z3INipeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AP41rnd1Sw3Eewokc.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;This is using the new, in 0.23.0-alpha.1, default&lt;/em&gt; &lt;em&gt;ZBSimpleLogger that outputs unstructured human-readable strings&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I had been ignoring this as “expected behaviour (&lt;em&gt;for now&lt;/em&gt;)”, and my &lt;a href="http://matizmo.com/the-importance-of-ras-and-its-implications-with-your-content-the-cocktail-party-phenomenon/"&gt;reticular activating system&lt;/a&gt; had conveniently made it invisible to me.&lt;/p&gt;

&lt;p&gt;The failure of the integration tests made it abundantly clear that this is actually &lt;em&gt;not&lt;/em&gt; expected behaviour in the formal specifications for the client (&lt;em&gt;the tests&lt;/em&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  DESIGNING FOR DEVELOPER UX
&lt;/h3&gt;

&lt;p&gt;This is bad Developer Experience (DX) design.&lt;/p&gt;

&lt;p&gt;When developers are using Zeebe Node for the first time against Camunda Cloud, they don’t know what they are doing &lt;em&gt;and&lt;/em&gt; they don’t know if they are doing it right.&lt;/p&gt;

&lt;p&gt;When it is not working as they expect, they don’t know whether it is that their expectation is erroneous, or they have done something incorrectly. Users are often unaware even that they &lt;em&gt;have&lt;/em&gt; a model of the system composed of expectations they hold as a hypothesis. Instead, they think: “&lt;em&gt;Something is wrong. This is not working.&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;There are four things that can be at the cause of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user has missed a step or made a mistake in their code or configuration.&lt;/li&gt;
&lt;li&gt;There is an intermittent failure condition (network down, service interruption).&lt;/li&gt;
&lt;li&gt;There is a bug in the library.&lt;/li&gt;
&lt;li&gt;The user’s expectation is incorrect (actual behaviour is correct, expected behaviour is incorrect).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Surfacing that last one — &lt;em&gt;the unexamined hypotheses that the user holds about the system as their working model of the system&lt;/em&gt; — is why bug reports request &lt;a href="https://knowthecode.io/labs/reordering-hook-not-working-first-post-problem-solving-lab/episode-3"&gt;“Expected Behaviour” and “Actual Behaviour”&lt;/a&gt; in their templates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Any&lt;/em&gt; message presented to the user while they are developing their model of the system &lt;em&gt;must&lt;/em&gt; take into account that the user’s model is unformed, and also that the user is usually not &lt;em&gt;consciously&lt;/em&gt; forming the model.&lt;/p&gt;

&lt;p&gt;I mean &lt;em&gt;any&lt;/em&gt; message. A DEBUG level informational message in the Zeebe broker log has been a source of confusion for new users. They frequently interpret it as an error message (&lt;a href="https://forum.zeebe.io/t/error-jobs-of-type-not-available-but-workflow-deploys-and-completes/1048"&gt;an example from the Zeebe Forum&lt;/a&gt;), and we are refactoring it to either reword it — taking into account the user’s uncertainty — or just take it out completely (&lt;a href="https://github.com/zeebe-io/zeebe/issues/3890"&gt;GitHub issue “Reword Blocking request DEBUG Message”&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This particular message in the Zeebe Node client — that &lt;em&gt;every single connection&lt;/em&gt; to Camunda Cloud &lt;em&gt;always&lt;/em&gt; emits an error — is &lt;strong&gt;terrible&lt;/strong&gt; for UX.&lt;/p&gt;

&lt;p&gt;New users have enough uncertainty already. This behaviour guarantees that &lt;em&gt;even if they do everything correctly&lt;/em&gt;, they are still going to be thinking: “&lt;em&gt;I’ve done something wrong, it’s not working.&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;And if they &lt;em&gt;have&lt;/em&gt; done something incorrectly and it is really not working, this message provides them with no clue as to what &lt;em&gt;that&lt;/em&gt; is, and will lead them on a &lt;a href="https://en.wiktionary.org/wiki/wild-goose_chase"&gt;wild-goose chase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It has to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  THE CURRENT GRPC STATE MACHINE
&lt;/h3&gt;

&lt;p&gt;The current gRPC connection state machine has evolved over time. It started off pretty simple, then evolved to support &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/35"&gt;client-side retry logic for network-related gRPC Error 14&lt;/a&gt; but &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/40"&gt;not business errors&lt;/a&gt;, emit &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/108"&gt;connection-level error events&lt;/a&gt;, a &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/71"&gt;ready event for the downstream Node-RED client&lt;/a&gt;, and &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/80"&gt;managing the gRPC Error 8 event&lt;/a&gt; for Broker back pressure response.&lt;/p&gt;

&lt;p&gt;As these features were added, the responsibility for managing the representation of the current connection state (that’s a &lt;a href="https://joshwulf.com/blog/2020/02/camunda-cloud-connection/(https://www.joshwulf.com/blog/2020/02/shun-the-mutant##valid-use-of-variables)"&gt;true variable in the code&lt;/a&gt;), debouncing transitions (&lt;em&gt;the channel state can flap about wildly during retries when it does go down — and this requires managing more state: time&lt;/em&gt;), and attaching various different retry and user-defined event handlers (&lt;em&gt;if you add one for worker job streaming to the same connection used for client commands, it gets called for both&lt;/em&gt;), got split between the ZBClient, ZBWorker, and GRPCClient classes. This happened because these various patches were made one after the other, with no refactoring.&lt;/p&gt;

&lt;p&gt;Now, adding the concept of “connection characteristics” will exponentially complicate already fragile code. State management is the bane of program correctness and a source of interminable bugs and edge conditions. State needs to be coalesced as an encapsulated concern in an application to avoid leaking this complexity into the other components in an application, and making changes to state management a distributed concern that touches many components.&lt;/p&gt;

&lt;p&gt;See the article “&lt;a href="https://www.joshwulf.com/blog/2020/02/avoid-global-state/"&gt;Avoiding Global Mutable State in Browser JS&lt;/a&gt;” and the “&lt;a href="https://www.joshwulf.com/blog/2020/02/shun-the-mutant##valid-use-of-variables"&gt;Valid use of variables&lt;/a&gt;” section in the article “&lt;a href="https://www.joshwulf.com/blog/2020/02/shun-the-mutant##valid-use-of-variables"&gt;Shun the mutant — the case for&lt;/a&gt;&lt;a href="https://www.joshwulf.com/blog/2020/02/shun-the-mutant##valid-use-of-variables"&gt;const&lt;/a&gt;” for more discussion about this.&lt;/p&gt;

&lt;p&gt;That implementing connection characteristics requires changes to multiple components is a &lt;a href="https://en.wikipedia.org/wiki/Code_smell"&gt;code smell&lt;/a&gt; that indicates there is a first-class concern with no, or an incorrectly bounded, materialised entity in the code.&lt;/p&gt;

&lt;p&gt;It is time for a significant refactor. The time required for this refactor is not trivial, and I conveniently ignored the error message until it became a failing test suite, then looked long and hard at the code before committing to this.&lt;/p&gt;

&lt;p&gt;This is a significant part of the Zeebe Node client — managing the state of the gRPC connection and providing an ergonomic API to signal and handle various failure conditions. However, you don’t have to be Einstein to realise that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We can not solve our problems with the same level of thinking that created them.” — Albert Einstein&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, adding the concept of “Connection Characteristics” with differential behaviours to a non-first class abstraction is asking for a million bug reports and an unending game of whack-a-mole where fixing one problem causes problems with other behaviours.&lt;/p&gt;

&lt;p&gt;No thanks. I’ll suck up the refactor costs.&lt;/p&gt;

&lt;p&gt;The code should model the domain. We now have in the domain “&lt;em&gt;various gRPC connection classes with different business rules for handling their characteristics&lt;/em&gt;”, so we are going to refactor the code to model that bounded context.&lt;/p&gt;

&lt;p&gt;Fortunately, I already have the behavioural specification written — my integration tests. If I change nothing in those, I just have to make Camunda Cloud’s integration test go green and keep the CircleCI one green, and I can be confident that it works as expected.&lt;/p&gt;

&lt;p&gt;As Evan Burchard explains, in his excellent book “&lt;a href="https://www.amazon.com/Refactoring-JavaScript-Turning-Code-Into/dp/1491964928"&gt;Refactoring JavaScript: Turning Bad Code into Good Code&lt;/a&gt;”:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When we have any code that lacks tests (or at least a documented way of executing it), we do not want to change this code. We can’t refactor it, because we won’t be able to verify that the behavior doesn’t change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  STEP ONE: EXPLAIN IT IN PLAIN LANGUAGE
&lt;/h3&gt;

&lt;p&gt;The first step is to write a specification in a non-executable language — aka plain English.&lt;/p&gt;

&lt;p&gt;This is the initial specification, and it should strive to be an unambiguous definition.&lt;/p&gt;

&lt;p&gt;I created a GitHub issue “&lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/133"&gt;Manage that Camunda Cloud connections &lt;em&gt;always&lt;/em&gt; error before eventually succeeding&lt;/a&gt;”, and described the motivation for the refactor in there.&lt;/p&gt;

&lt;p&gt;Then, I added a &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js/issues/133#issuecomment-592862739"&gt;design specification as a comment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am going to separate the functionality into two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The underlying GRPCClient that manages the gRPC connection and emits state events.&lt;/li&gt;
&lt;li&gt;An intermediary Business Rules component that models the connection characteristics and ensures that various connections behave the same at the API level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one concern whose location I am not yet clear about. I am thinking that the GRPCClient will have a method to turn on a buffering mode, where it buffers state transition events, and then returns the eventual state with a list of intermediate transitions, after a specified period of time.&lt;/p&gt;

&lt;p&gt;This allows me to turn this on for Camunda Cloud connections, and throw away those initial failures, reporting only the ultimate result.&lt;/p&gt;

&lt;p&gt;This may complicate the GRPCClient state machine by adding an additional state machine to it (&lt;em&gt;managing time, and the current operational mode&lt;/em&gt;), so I may move this into the Connection Characteristics component. We shall see. It’s a working hypothesis. Gotta start with some kind of vision, and keep re-evaluating along the way.&lt;/p&gt;

&lt;p&gt;OK, let the fun begin!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About me&lt;/strong&gt; : &lt;em&gt;I’m a Developer Advocate at&lt;/em&gt; &lt;a href="https://camunda.com/"&gt;&lt;em&gt;Camunda&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, working primarily on the&lt;/em&gt; &lt;a href="https://zeebe.io/"&gt;&lt;em&gt;Zeebe Workflow engine for Microservices Orchestration&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, and the maintainer of the&lt;/em&gt; &lt;a href="https://www.npmjs.com/package/zeebe-node"&gt;&lt;em&gt;Zeebe Node.js client&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. In my spare time, I build&lt;/em&gt; &lt;a href="https://github.com/Magikcraft"&gt;&lt;em&gt;Magikcraft&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, a platform for programming with JavaScript in Minecraft.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>camunda</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Avoiding mutable global state in browser JS</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Tue, 25 Feb 2020 09:18:02 +0000</pubDate>
      <link>https://forem.com/jwulf/avoiding-mutable-global-state-in-browser-js-1e31</link>
      <guid>https://forem.com/jwulf/avoiding-mutable-global-state-in-browser-js-1e31</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6647cwy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Akjy3lIrPQZFtZaNu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6647cwy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Akjy3lIrPQZFtZaNu.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of&lt;/em&gt; &lt;a href="https://www.joshwulf.com/categories/stackoverflowed/"&gt;&lt;em&gt;a series of posts&lt;/em&gt;&lt;/a&gt; &lt;em&gt;where I refactor code from StackOverflow questions, with a discussion of the changes. One of the great things about JavaScript is how scalable it is. You can start with a simple script, and there is nothing wrong with that. Usually these posts are about refactorings other than what the questioner asked about, and would be out of scope for the SO answer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The accompanying GitHub repo for this article can be found &lt;a href="https://github.com/jwulf/immutable-global-store"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Global scope is a feature of browser JavaScript that is a source of application-spanning bugs (it &lt;em&gt;is&lt;/em&gt; global). Global state doesn’t just impact the whole application — it creates a &lt;em&gt;entire new surface area&lt;/em&gt; for bugs &lt;em&gt;across the entire code base&lt;/em&gt;, that has to be managed. Bugs related to global state can happen &lt;em&gt;anywhere&lt;/em&gt;. The number of potential bugs in &lt;em&gt;every function&lt;/em&gt; increases as soon as you have global state.&lt;/p&gt;

&lt;p&gt;Any local function can mess with the functioning of any other function by mutating global scope, and this can result in bugs that are hard to track down to their source.&lt;/p&gt;

&lt;p&gt;In this refactoring we are not going to be able to completely eliminate global state — mostly because we don’t have enough information about how the state will be used in the rest of the application to make a recommendation for an alternative.&lt;/p&gt;

&lt;p&gt;What we will do is reduce the bug surface area significantly. And along the way, you’ll be introduced to some of the concepts underlying React.setState and Redux.&lt;/p&gt;

&lt;h3&gt;
  
  
  THE QUESTION
&lt;/h3&gt;

&lt;p&gt;Here is the code from StackOverflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;memArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;//object   &lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;memObj1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;memArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memObj1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DISCUSSION
&lt;/h3&gt;

&lt;p&gt;There is a lot going on this example that can be refactored, and we’ll look at a number of things in other articles. But for now, let’s look at global state.&lt;/p&gt;

&lt;h3&gt;
  
  
  MEMARRAY
&lt;/h3&gt;

&lt;p&gt;The global memArray has two immediate issues - apart from being global.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;var&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, it is declared as var, which means that it can be reassigned at runtime.&lt;/p&gt;

&lt;p&gt;In fact, using var is a declaration to the machine and to other programmers that “&lt;em&gt;I intend that the value of this assignment change over the course of execution&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;It may be that the novice programmer misunderstands assignment of arrays in JS. Making this a var doesn't make the &lt;em&gt;contents&lt;/em&gt; of the array mutable - you have to do real deliberate work to make them immutable. Rather, declaring this as var makes &lt;em&gt;the assignment itself mutable&lt;/em&gt;. Meaning that memArray itself can be mutated by pointing it to something other than the array you just created and assigned to it.&lt;/p&gt;

&lt;p&gt;Somewhere deep in the code, a function could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;memArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could be because another programmer uses it as a local variable name with no declaration, in which case the runtime will use the previously declared global variable. You won’t get a warning from your tools about using an undeclared variable, because &lt;em&gt;it is declared&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And this bug in one function somewhere, that maybe doesn’t even use this global state (&lt;em&gt;it probably doesn’t, or the programmer wouldn’t have reused the variable name&lt;/em&gt;), just broke &lt;em&gt;everything&lt;/em&gt; that does use it. And when you go to hunt it down, it is not in any of your functions that &lt;em&gt;do&lt;/em&gt; use the global state.&lt;/p&gt;

&lt;p&gt;The chances of this happening are increased because of the second issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Naming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See this article about &lt;a href="https://www.joshwulf.com/blog/2020/02/just-say-no-to-loops-and-variables/"&gt;the importance of naming&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In code examples on StackOverflow, I always name global variables like this: EvilGlobalMembersArray.&lt;/p&gt;

&lt;p&gt;There is no way someone is accidentally reusing that in a local scope. At the very least, GlobalMembersArray is an unambiguous name that communicates what it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  FIRST REFACTOR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMembersArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it a const so that it cannot be reassigned, and give it a meaningful and useful name. This is “naming by convention” that takes away cognitive load when reading the code.&lt;/p&gt;

&lt;p&gt;If I find a reference to GlobalMembersArray in a function deep in the code, I immediately know what I am looking at, and I'm not using that name for a local variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  MUTATION
&lt;/h3&gt;

&lt;p&gt;The global is now &lt;em&gt;not&lt;/em&gt; reassignable, &lt;em&gt;and&lt;/em&gt; unambiguously named, which reduces the chances of someone accidentally reusing it. Since it is an array, they cannot change the reference to point to another array, object, or primitive, but they &lt;em&gt;can&lt;/em&gt; still mutate the contents.&lt;/p&gt;

&lt;p&gt;You want that, right? Presumably, we are going to want to add to, remove from, and update elements in this array.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;No&lt;/em&gt;. By exposing just the array globally, we have &lt;em&gt;devolved responsibility&lt;/em&gt; for mutating it to local functions in the application.&lt;/p&gt;

&lt;p&gt;That concern, and hence the complexity of it, is now spread throughout the application. Bugs related to mutating the array values can appear anywhere in the application, at any time. And again, they can be hard to track down, because they will likely appear when a function uses the array and doesn’t find what it expects — rather than where the bug exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  SECOND REFACTOR — IIFE
&lt;/h3&gt;

&lt;p&gt;Rather than expose an array, we should expose an object that encapsulates the state, &lt;em&gt;plus&lt;/em&gt; mutation methods. And we will not expose the actual state, because local functions can still and may be tempted to mutate it directly. Instead we will return &lt;em&gt;a copy of the state&lt;/em&gt;, so that the only way to update it is via the object methods.&lt;/p&gt;

&lt;p&gt;We can do this using an IIFE — an &lt;a href="https://en.wikipedia.org/wiki/Immediately_invoked_function_expression"&gt;Immediately Invoked Function Expression&lt;/a&gt;, a JavaScript function that immediately executes and can return an object that has a private scope inside a closure.&lt;/p&gt;

&lt;p&gt;In terms of ES6 classes, it is roughly analogous to creating an instance of a class that has private methods.&lt;/p&gt;

&lt;p&gt;Here it is with no accessors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the enclosing () and the immediate invocation: (() =&amp;gt; {})().&lt;/p&gt;

&lt;p&gt;In this case, we will get back an Object with no properties. But what you want to know is that it also contains a hidden array — _members - that cannot be accessed by local functions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But, but… aren’t you the “&lt;/em&gt;&lt;a href="https://www.joshwulf.com/blog/2020/02/just-say-no-to-loops-and-variables/"&gt;&lt;em&gt;Just Say No to Variables&lt;/em&gt;&lt;/a&gt;&lt;em&gt;” guy? What is that&lt;/em&gt; &lt;em&gt;let statement doing there?!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Look, we &lt;em&gt;can&lt;/em&gt; remove variables completely. But we don’t have enough information about the eventual application to do that. So what I’ve done here is take a global variable, and put inside a closure where &lt;em&gt;it is invisible to the rest of the application&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;All the complexity and bug surface area will be behind the singularity of the closure, with an immutable API. There will be no variables exposed to the rest of the application. And the resulting code is &lt;a href="https://github.com/jwulf/immutable-global-store/blob/master/test.spec.js"&gt;fully unit-testable&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMPLEMENTING GETMEMBERS
&lt;/h3&gt;

&lt;p&gt;Now we will provide a method to return &lt;em&gt;a copy&lt;/em&gt; of the _members array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://javascript.info/rest-parameters-spread#spread-syntax"&gt;ES6 spread syntax&lt;/a&gt; — [...members] - &lt;em&gt;spreads&lt;/em&gt; the contents of the local members array into a new array, and returns that.&lt;/p&gt;

&lt;p&gt;Local functions can add things to the array, or delete elements, but these operations do not affect the global state, because they have &lt;em&gt;a copy&lt;/em&gt; of the global state, not a reference to the global state.&lt;/p&gt;

&lt;p&gt;Note, however, that because the elements of the array are &lt;em&gt;objects&lt;/em&gt;, local functions can still mutate members within the copy, and that &lt;em&gt;will&lt;/em&gt; affect the global state — because the array elements are references to objects. The internal state array and the copy we just returned are &lt;em&gt;different&lt;/em&gt; arrays, but they contain references to the &lt;em&gt;same&lt;/em&gt; member objects&lt;/p&gt;

&lt;p&gt;We can avoid that scenario like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"&gt;Array.map&lt;/a&gt; returns a new array, so the consumer has no reference to the global state array. The new array is populated by applying the &lt;a href="https://en.wikipedia.org/wiki/Functional_predicate"&gt;&lt;em&gt;predicate function&lt;/em&gt;&lt;/a&gt; to each value in the original array, and putting the return value in the new array.&lt;/p&gt;

&lt;p&gt;It is “make a new array by applying this transform to each element in this other array”.&lt;/p&gt;

&lt;p&gt;In the predicate function — m =&amp;gt; ({...m}) - we return a &lt;em&gt;copy&lt;/em&gt; of each member object from the _members array, again using the ES6 Spread syntax, this time on an object.&lt;/p&gt;

&lt;p&gt;When you return an object in a one-liner arrow function, you need to put () around it so the interpreter doesn't interpret the contents of {} as function code, but knows that it is an object, so: m =&amp;gt; ({...m}).&lt;/p&gt;

&lt;p&gt;Now we have a new array, and new objects in the array.&lt;/p&gt;

&lt;p&gt;Local functions now have access to the &lt;em&gt;value&lt;/em&gt; of the global members state, but the actual global state is immutable by them, because they have no reference to it. They cannot update the global state from the copy that they get. For that, they will need to call an update method.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMPLEMENTING SETMEMBERS
&lt;/h3&gt;

&lt;p&gt;The first method we will implement is a hydration method that allows a local function to pass in an array of members.&lt;/p&gt;

&lt;p&gt;I’ll take out getMembers for now to make it easier to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the Spread syntax to copy the members to a new array, and this becomes the global members.&lt;/p&gt;

&lt;p&gt;This means that a local function cannot set the global state by passing in an array of members, and then mutate the global state by mutating one of the members that it passed in.&lt;/p&gt;

&lt;p&gt;If we did a naive assignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;setMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the local function calling this method would have a local reference to the member objects that are now in the state store. By spreading them, we make a copy — another object in memory that the local function has no reference to.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMPLEMENTING UPDATEMEMBER
&lt;/h3&gt;

&lt;p&gt;It is likely that a business requirement for this application is that you can update a member.&lt;/p&gt;

&lt;p&gt;So, we will implement an updateMember function. We will use Array.map to return a new array. A naive approach to this might be “&lt;em&gt;let's iterate over the array using&lt;/em&gt; &lt;em&gt;forEach and mutate the element we are updating&lt;/em&gt;". See the post “&lt;a href="https://www.joshwulf.com/blog/2020/02/just-say-no-to-loops-and-variables/"&gt;Just Say No to Loops and Variables&lt;/a&gt;” for an in-depth explanation of why you &lt;em&gt;don't&lt;/em&gt; want to do that.&lt;/p&gt;

&lt;p&gt;To implement the predicate function, let’s describe what we want it to do in plain language:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For each member in the state,&lt;/p&gt;

&lt;p&gt;if the member id equals the id of the update, return the update;&lt;/p&gt;

&lt;p&gt;otherwise return the member.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, our predicate function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator"&gt;ternary operator&lt;/a&gt; here to implement if-then-else in a single expression.&lt;/p&gt;

&lt;p&gt;We can probably shorten the name we use for member to m, because the context is sufficient to provide information about what it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;updateMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We enclose the assignment operation _members = in parens () to indicate that we did not forget to return a value, and intended only the side-effect. We could have put it in {}, but that will cause code formatters to turn our single line into three.&lt;/p&gt;

&lt;h3&gt;
  
  
  DESIGNING FOR FAILURE
&lt;/h3&gt;

&lt;p&gt;20% of programming is getting it to work. The other 80% is programming for &lt;em&gt;when it doesn’t work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What happens if a local function requests to update a member who is not in the state? At the moment, the local function receives no information from the call to updateMember, and if you look at the code, what will happen is… nothing.&lt;/p&gt;

&lt;p&gt;The predicate function will never match, and the new state will be a new copy of the existing state, unmodified.&lt;/p&gt;

&lt;p&gt;We could throw an exception. This gives us the opportunity to figure out where the bug in the application is that it is trying to update a member that doesn’t exist. This is a good idea.&lt;/p&gt;

&lt;p&gt;Let’s throw an exception so that the root cause can be debugged in the local function. To do this, we will need a getMember function that we can use. So, let's implement that.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMPLEMENTING GETMEMBER
&lt;/h3&gt;

&lt;p&gt;It’s likely that local functions will want only a single member. If we don’t implement it here, we will have local functions retrieving the entire state and filtering it. This leaks complexity into the application, because we &lt;em&gt;can&lt;/em&gt; do that in “one place, and one place only” in the application: &lt;em&gt;here&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Then we only have to test it in one place, and we only ever have to get it to work in one place. That reduces the surface area for bugs in the application.&lt;/p&gt;

&lt;p&gt;We can use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"&gt;Array.filter&lt;/a&gt; to find elements in an array. Array.filter returns a new array containing only the elements from the original array for whom the predicate function returned true.&lt;/p&gt;

&lt;p&gt;The predicate function is straight forward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return true if the member.id equals the requested id;&lt;/p&gt;

&lt;p&gt;otherwise, return false&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reducing that down, we get:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return member.id equals requested id&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The getMember array will now return an array with either zero (if no member with that id exists in the state) or one… hang on, what happens if there is more than one member in the array with the same id? In that case it will return more than one member.&lt;/p&gt;

&lt;p&gt;Probably, the business requirement is that member id is unique. So we will take that into account when we write the addMember function.&lt;/p&gt;

&lt;p&gt;So it will return an array with 0 or 1 members in it. Probably local functions want a member or undefined.&lt;/p&gt;

&lt;p&gt;Although, we can provide a better API if we return an object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Member&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then consumers of this API using TypeScript can use a &lt;a href="https://medium.com/@sitapati/implementing-a-maybe-pattern-using-a-typescript-type-guard-81b55efc0af0"&gt;Type Guard&lt;/a&gt; to get safety against accessing an undefined value, and our API forces them to use it.&lt;/p&gt;

&lt;p&gt;This reduces bugs. Otherwise, we are relying on every local function in the application remembering to test it for undefined before accessing it - another surface area for bugs.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to Spread the member to return a copy (I picked this up when the test case failed &lt;a href="https://github.com/jwulf/immutable-global-store/blob/master/test.spec.js#L109"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Nice API.&lt;/p&gt;

&lt;h3&gt;
  
  
  THROWING ON IMPOSSIBLE UPDATE
&lt;/h3&gt;

&lt;p&gt;Another significant advantage of this approach is that we put all our business validation rules about the data in a single place: in the store. They are not spread throughout the application, and the responsibility of everyone and no-one. They can be put in one place, tested automatically, updated in one place, and if a local function violates them, we will find out immediately when it tries to store the data, through an exception.&lt;/p&gt;

&lt;p&gt;We can now consume getMember from our own API to guard against an update error.&lt;/p&gt;

&lt;p&gt;How can we do that? We need to lift our API to its own context inside the closure, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a private reference to our own API, as Store. So we can use it to see if the member that the local function wants to update, actually exists - and if not, throw.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;updateMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`No member with id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in the store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IMPLEMENTING PUTMEMBER
&lt;/h3&gt;

&lt;p&gt;Probably, a business requirement of the application will be to put a new member in the store.&lt;/p&gt;

&lt;p&gt;We have to make a decision here about the behaviour of the store. What happens if a local function attempts to put a member with an id that is already in the store?&lt;/p&gt;

&lt;p&gt;That’s probably a bug somewhere further upstream in the application logic, so we will throw an exception to allow debugging to start.&lt;/p&gt;

&lt;p&gt;So we can do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;putMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already exists!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;updateMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;needsMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`No member with id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in the store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DEALING WITH A UNDEFINED ID
&lt;/h3&gt;

&lt;p&gt;Another potential bug that we can detect here is a local function passing in either undefined or a member with an id that is undefined.&lt;/p&gt;

&lt;p&gt;We can write helper functions for this, and call them on all operations where it is a requirement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needsArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;  &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Undefined passed as argument to Store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needsId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Undefined id on member passed **as** argument to Store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how we use this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;putMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;needsId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;  &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already exists!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FREEZE!
&lt;/h3&gt;

&lt;p&gt;For our final touch, we are going to freeze the API object using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze"&gt;Object.freeze&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents anyone from overwriting or modifying the API methods themselves.&lt;/p&gt;

&lt;p&gt;If you wanted, you could (deep) freeze all the return values from the API methods. That would deny local function consumers of the objects the ability to mutate the return values. They would need to use spread on them. We’re not going to do that right now.&lt;/p&gt;

&lt;p&gt;Freezing objects has an impact on performance. Freezing the API is not going to make a huge difference, so the safety is worth it. The objects returned from the API are copies, so freezing them is overkill, IMHO.&lt;/p&gt;

&lt;h3&gt;
  
  
  PUTTING IT ALL TOGETHER
&lt;/h3&gt;

&lt;p&gt;Here is the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalMemberStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needsArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Undefined passed as argument to Store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needsId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Undefined id on member passed as argument to Store!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}))),&lt;/span&gt;
    &lt;span class="na"&gt;getMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;putMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;needsId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already exists!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;updateMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;needsId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;  &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; does not exists!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;_members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may seem like way more complexity than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;memArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this is the &lt;em&gt;actual&lt;/em&gt; complexity involved in this data structure in the application. &lt;em&gt;You will end up doing all of this anyway&lt;/em&gt; — but it will be spread throughout your application in manipulation and mutation of that array, and if statements, and fixing bugs in various places.&lt;/p&gt;

&lt;p&gt;And it will be really hard to refactor in the future.&lt;/p&gt;

&lt;p&gt;With this approach, the total technical complexity of this concern is now encapsulated in one place in your application. It is testable through automated tests — &lt;a href="https://github.com/jwulf/immutable-global-store/blob/master/test.spec.js"&gt;as demonstrated in the accompanying repo&lt;/a&gt;. There are 125 lines of test code for 40 lines of code. So 165 lines of code to replace var memArray = [].&lt;/p&gt;

&lt;p&gt;However, business validation of the data now has a place to live, and the entire expected usage of this array is now implemented such that local functions cannot introduce bugs related to it — only their local use of it.&lt;/p&gt;

&lt;h1&gt;
  
  
  winning
&lt;/h1&gt;

&lt;h3&gt;
  
  
  FURTHER RESOURCES
&lt;/h3&gt;

&lt;p&gt;This approach to state management has become popular in JS in recent years, and is the basis of the approach used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/docs/state-and-lifecycle.html"&gt;React&lt;/a&gt;&lt;a href="https://reactjs.org/docs/state-and-lifecycle.html"&gt;setState&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redux.js.org/introduction/getting-started/"&gt;Redux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freecodecamp.org/news/an-introduction-to-the-flux-architectural-pattern-674ea74775c9/"&gt;Flux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/immutable"&gt;Immutable.JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ohager/nanoflux"&gt;Nanoflux&lt;/a&gt; (&lt;em&gt;My personal favorite&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you grasped the concepts and rational for the refactorings that I made in this example, you will be well-placed to understand these mature, more sophisticated (and generalised) implementations.&lt;/p&gt;

</description>
      <category>browsers</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Just Say No to Loops and Variables</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Sun, 23 Feb 2020 07:46:04 +0000</pubDate>
      <link>https://forem.com/jwulf/just-say-no-to-loops-and-variables-58a7</link>
      <guid>https://forem.com/jwulf/just-say-no-to-loops-and-variables-58a7</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a1nri8ki--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AlS1iF2TwwMJNBnc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a1nri8ki--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AlS1iF2TwwMJNBnc0.png" alt=""&gt;&lt;/a&gt;StackOverflow — Where developers learn, share, and build their careers&lt;/p&gt;

&lt;p&gt;Recently, I spent some time on StackOverflow, helping people with their school assignments — I mean, serious programming questions they had at work. (I’m pretty sure a fair whack of them were homework assignments).&lt;/p&gt;

&lt;p&gt;One thing that came out of it — for me — was a pattern in the issues in the JavaScript programming tag (&lt;em&gt;Discord bots are hot right now with the kids&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;There are certain things that people struggle with when learning to program, and when learning to program in JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronicity&lt;/strong&gt; is one. Callbacks not so much — mostly now people are struggling with Promises (with are a Monadic wrapper around an asynchronous operation), and with the subtle context impedance mismatch between async functions and non-async functions. Without TypeScript informing them of the type mismatch, they are baffled by code that is in a monadic async context interacting with code that is not. I mean: &lt;em&gt;they look the same&lt;/em&gt;. At least with callbacks and Promises you have some clue in the indentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming&lt;/strong&gt; is another one. The power of correctly naming entities in a program cannot be overestimated — I mean, it’s one of the two hardest problems in computer science: caching, naming things, and whether or not to move to San Francisco.&lt;/p&gt;

&lt;p&gt;The impact of &lt;em&gt;not&lt;/em&gt; correctly naming things cannot be overestimated either. Confusing messes of spaghetti code where the programmer had wound themselves up in a ball of yarn and not only gotten trapped inside it, but forgotten what they were trying to do in the first place. In the end, I started to have some fun with it, telling one questioner that “&lt;em&gt;80% of programming is correctly naming things, and the other 20% is choosing the font for your IDE.&lt;/em&gt;” &lt;a href="https://www.jetbrains.com/lp/mono/"&gt;JetBrains Mono&lt;/a&gt;. (I solved his problem for him.) He had started with an entity named x and was now three levels deep trying to figure out how to iterate the data structure. The Tao becomes clear when you know that for each recipe we have an array of ingredients, and each ingredient has a set of attributes that characterize it.&lt;/p&gt;

&lt;p&gt;As we read in &lt;a href="https://china.usc.edu/confucius-analects-13"&gt;the Analects of Confucius&lt;/a&gt; (“&lt;em&gt;Confucius say&lt;/em&gt;”):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tsze-lu said, “The ruler of Wei has been waiting for you, in order with you to administer the government. What will you consider the first thing to be done?”&lt;/p&gt;

&lt;p&gt;The Master replied, “What is necessary is to rectify names.” “So! indeed!” said Tsze-lu. “You are wide of the mark! Why must there be such rectification?”&lt;/p&gt;

&lt;p&gt;The Master said, “How uncultivated you are, Yu! A superior man, in regard to what he does not know, shows a cautious reserve.&lt;/p&gt;

&lt;p&gt;“If names be not correct, language is not in accordance with the truth of things. If language be not in accordance with the truth of things, affairs cannot be carried on to success.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Computer programming is an exercise in applied linguistics. It is precisely specifying the execution of operations to transform matter through the utterance of magic spells. Say the wrong thing, and &lt;em&gt;BOOM!&lt;/em&gt; you turn into a toad. The stakes are high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixing concerns&lt;/strong&gt; is another common one. A symptom of the confusion that arises from this — and I really do mean &lt;em&gt;confusion&lt;/em&gt; here: the &lt;em&gt;dosha&lt;/em&gt;, or philosophical error described in the Sanskrit logical system of &lt;em&gt;Nyaya&lt;/em&gt; as &lt;em&gt;bhranti darshana&lt;/em&gt;: a mistaken perception, literally: “an illusory vision”. For example: thinking that a rope is a snake. That is &lt;em&gt;confused&lt;/em&gt;: two things are &lt;em&gt;fused with&lt;/em&gt; each other in a way that they are no longer distinct, and one is mistaken for the other.&lt;/p&gt;

&lt;p&gt;In Sanskrit philosophy, there is an entire school — &lt;em&gt;Samkhya&lt;/em&gt; — dedicated to the study of &lt;em&gt;separation of concerns&lt;/em&gt;. &lt;em&gt;Samkhya&lt;/em&gt; is sometimes translated as “categorisation” or “distinction”.&lt;/p&gt;

&lt;p&gt;According to Wikipedia:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Samkhya means “to reckon, count, enumerate, calculate, deliberate, reason, reasoning by numeric enumeration, relating to number, rational.” In the context of ancient Indian philosophies, Samkhya refers to the philosophical school in Hinduism based on systematic enumeration and rational examination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It comes from two words: &lt;em&gt;Sam&lt;/em&gt; meaning “the whole” or “totality” (from which the English word &lt;em&gt;sum&lt;/em&gt; comes to us), and &lt;em&gt;khya&lt;/em&gt; meaning &lt;em&gt;to name&lt;/em&gt;. The founders of this philosophical system were totally into enumerating everything categorically, and describing the relationships between categories as an access to understanding the whole.&lt;/p&gt;

&lt;p&gt;In modern software development &lt;a href="https://en.wikipedia.org/wiki/Separation_of_concerns"&gt;separation of concerns&lt;/a&gt; is a widely accepted best practice for reducing complexity and technical debt.&lt;/p&gt;

&lt;h3&gt;
  
  
  MIXED CONCERNS LEAD TO EXPONENTIAL COMPLEXITY
&lt;/h3&gt;

&lt;p&gt;One thing I noticed many novice programmers struggling with was the mixing of the concerns of data transformation — essentially a functional concern — with imperative flow control.&lt;/p&gt;

&lt;p&gt;Nothing wrong with that, but it lead them into situations where they experienced overwhelming complexity. They couldn’t get the data transformation that they wanted, &lt;em&gt;and&lt;/em&gt; they were struggling with building a custom state machine to produce it at the same time. The intersection of these two problems lead them to throw up their hands and turn to StackOverflow.&lt;/p&gt;

&lt;p&gt;As I said to one questioner: “&lt;em&gt;when you solve a problem using loops and variables, now you have three problems&lt;/em&gt;”. You have to build a custom state machine, track mutable state, &lt;em&gt;and&lt;/em&gt; you still have the original problem you were trying to solve.&lt;/p&gt;

&lt;p&gt;Now, seasoned programmers can often look at these trivial (to them) situations and see a clear way out of the scenario the new programmer has gotten themselves into, and guide the novice to correctly construct the state machine, correctly initialise and scope the variables, and get the desired data transformation with the resulting system — and you might think “&lt;em&gt;what’s the big deal?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;It can even be a mark of pride to be able to tame it in this way. However, the novice programmer’s dilemma is a microcosm that simply scales up when they continue to code this way with more prowess.&lt;/p&gt;

&lt;h3&gt;
  
  
  SO, THE HOT TAKE
&lt;/h3&gt;

&lt;p&gt;I overstepped the mark in one question. This particular week, I was in the top 0.11% of StackOverflow contributors worldwide, as recognized by the community on StackOverflow (thank you, you’re welcome), and on a burn.&lt;/p&gt;

&lt;p&gt;To one question, I said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can see two problems with your code straight away:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Loops&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote a solution that used no custom state machine (no loops), and no mutable variables.&lt;/p&gt;

&lt;p&gt;Another StackOverflow contributor wrote a functioning custom state machine with mutation that also solved the problem domain, and I commented:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also, loops and variables! The more of those you do, the more complexity and moving parts you have to debug. Solid effort though. Pretty cool using a RegEx.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To which he took affront — fair enough. He said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a class="comment-mentioned-user" href="https://dev.to/josh"&gt;@josh&lt;/a&gt;
 Wulf — There is no reason not to use loops and variables in code. You are ridiculous. You should know that using certain convenience functions of Array are slower performing than using for loops. Filter is actually a loop that iterates through all elements of an array, and it performs slower than for loops across the board. I recommend keeping your comments constructive and avoid passive agressive remarks like “Solid effort though.” Its actually a viable solution for many cases. I even pointed out that another solution on the thread might be better. Maybe write a blog where you critique?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ouch!&lt;/p&gt;

&lt;p&gt;I apologised to him, because I did overstep the mark with that comment. It’s fine to have a perspective on something, but leaving a member of our professional community, who is giving their free time to contribute to others, with the experience of being disrespected is not what I am committed to.&lt;/p&gt;

&lt;p&gt;So, I apologised, and accepted his request to write a blog article about it. Thank you to that member of our community for holding me to account to the level of professional courtesy and respect that you are due, and for the opportunity to write this blog.&lt;/p&gt;

&lt;p&gt;Here we go:&lt;/p&gt;

&lt;h3&gt;
  
  
  STATE MUTATION AND COMPLEXITY
&lt;/h3&gt;

&lt;p&gt;Mutable state in a program is &lt;em&gt;additional complexity&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;More variables means more moving parts: &lt;em&gt;mo’ vars mo’ problems&lt;/em&gt;. If an assignment is declared as mutable, guarantees about that assignment are weakened. This means that reasoning about the eventual value of that assignment in other parts of the program is complex. With TypeScript (on a strong setting), the transpiler will make a strong assertion about the &lt;em&gt;type&lt;/em&gt; of the value, to reduce complexity, but it cannot make any guarantees about its eventual &lt;em&gt;value&lt;/em&gt;. Without TypeScript, &lt;em&gt;neither&lt;/em&gt; is guaranteed. (And at run-time, all bets are off, so you are at the mercy of the accuracy and consistency of your typings).&lt;/p&gt;

&lt;p&gt;Deliberately reducing complexity by choosing to eschew the mutant is a programming discipline, and one that I believe pays off.&lt;/p&gt;

&lt;p&gt;Douglas Crockford wrote the famous book &lt;a href="https://www.amazon.com.au/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742"&gt;JavaScript: The Good Parts&lt;/a&gt;, where he shared his discovery that if he deliberately avoided certain parts of the language — artificially constraining himself to a subset of the language — his productivity improved.&lt;/p&gt;

&lt;p&gt;I believe that variables belong in the category of “things to avoid”.&lt;/p&gt;

&lt;p&gt;I took on programming without variables, and there has only been case where the word let has left my mouth in the past two years:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**let** result
**try** {
    result = **await** asyncOpThatMayThrow()
} **catch** (e) {
**return** handle(e)
}

**try** {
**await** useResult(result)
} **catch** (e) {
**return** handleThis(e)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is something that I have grappled with, because it is at the intersection of another programming discipline that I adopted: striving for a single level of indentation. Memories of grappling with deeply nested code bases, trying to figure out which level got unbalanced, and ending up with code that would again lint and run, but that I wasn’t sure still produced the same effects, lead me to that.&lt;/p&gt;

&lt;p&gt;I recently resolved this, and that let is no more for me - but that is another blog post.&lt;/p&gt;

&lt;p&gt;I see novice programmers declaring variables as let and var, sometimes interchangably in the same code, with no reassignment of their value in the scope. Why would you do that? These declarations communicate your intent to the machine and other programmers: “&lt;em&gt;I intend that the value of this assignment change over the course of execution&lt;/em&gt;". When you don't change it, why communicate that intent? You have incorrectly named a thing.&lt;/p&gt;

&lt;p&gt;And when you do mutate it, you make it necessary for the machine and more importantly, for other programmers to then trace the flow of execution through the code base to reason about its value in different places.&lt;/p&gt;

&lt;p&gt;And when you make a coding error, and accidentally mistype a variable name (because you gave them non-descriptive, or similar names), you just created a case of mistaken identity mutation bug in the program. And no reasoner can detect your unintended mistake and warn you of it, because &lt;em&gt;variables&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Just say No to variables. Try it for one year (&lt;em&gt;I know that seems like a long time if it represents a significant percentage of your programming career to date&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;If you are a new programmer struggling to get your data transformation to work, reduce the complexity — take out one of the variables: variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  CUSTOM STATE MACHINES: LOOPS
&lt;/h3&gt;

&lt;p&gt;Loops are problematic in several ways.&lt;/p&gt;

&lt;p&gt;Oftentimes, armed with the loop and an array of data to transform, a novice programmer will frame the problem as: “&lt;em&gt;I have to transform every element in this array&lt;/em&gt;”. So they make a loop, with side effects. If those side-effects are asynchronous, now they are dealing with three problems.&lt;/p&gt;

&lt;p&gt;That’s an explosion of complexity, and leads to complex and fragile constructions that are resistant to refactoring. As the novice (or maintenance) engineer iterates on the data transformation taking place in the loop, the coupling of the state machine with the data transformation can cause the state machine to break, or to require a change in the state machine to accomodate a change in the data transformation. This is especially problematic for the novice programmer who is trying to get both to work at the same time. Now they are solving a two variable problem when they started with one!&lt;/p&gt;

&lt;p&gt;Niklaus Wirth’s classic work on programming distinguished two categories: &lt;a href="https://www.amazon.com/Algorithms-Data-Structures-Niklaus-Wirth/dp/0130220051"&gt;Algorithms and Data Structures&lt;/a&gt;. A third concern in a program is &lt;em&gt;control flow&lt;/em&gt; — in distributed systems it is &lt;em&gt;processes&lt;/em&gt; — directed evolution of the program (system) state over time.&lt;/p&gt;

&lt;p&gt;By using a loop, you are putting all three in one place. Many novice programmers (and experienced ones working on new systems) are operating without a clear picture of the eventual shape of the data that they need to model the state and the transformations required to achieve the outcome. When you put all three in one place, you now have a three-variable equation that you are trying to solve at once.&lt;/p&gt;

&lt;p&gt;And you are doing it by building the machine that will apply the transformation to the data structure, manually.&lt;/p&gt;

&lt;p&gt;This, I believe, is at the core of the breakdown for many of the novice programmers who loop themselves into a knot with these assignments — I mean, work problems. They end up going: “&lt;em&gt;What the heck am I even doing???&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;The complexity is &lt;em&gt;too much&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And what got missed, right at the outset, is that the problem is &lt;strong&gt;not&lt;/strong&gt; “&lt;em&gt;apply a transformation to every element in this array&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;That is the automatic GOTO (&lt;em&gt;sorry, couldn’t resist&lt;/em&gt;) of the programmer armed with a loop.&lt;/p&gt;

&lt;p&gt;The problem is in fact, much, much simpler. It is: “&lt;em&gt;apply a transformation to&lt;/em&gt; &lt;strong&gt;&lt;em&gt;each&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;element in this array&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;Once this is grasped, the separation of concerns becomes clearer:&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;I need to write a data transformation function that takes one element and returns one transformed element&lt;/em&gt;.”&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;And I need to apply this transformer function to each element in the array&lt;/em&gt;.”&lt;/p&gt;

&lt;p&gt;The problem has suddenly reduced in both scope and intersectional complexity.&lt;/p&gt;

&lt;p&gt;The state machine and the transformation are now separate concerns, whose complexity can be reduced independently.&lt;/p&gt;

&lt;p&gt;Loops are imperative control flow constructs. They can be used well, but they are dangerous. They couple control flow with transformations / side effects. I believe they cause more harm than good, especially for novice programmers, because they obscure the separation of concern.&lt;/p&gt;

&lt;p&gt;Compare this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**function**  **countNumbers** (arr) {
**var** count = 0;
**for** (num **in** arr) {
**if** (Number(arr[num]) !== NaN) {
            count++;
        }
    }
**return** count;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**const** isNum = n =&amp;gt; !isNaN(parseInt(n));
**const** countNumbers = arr =&amp;gt; arr.filter(isNum).length;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the second, the two concerns are separated and named. They are not intermixed.&lt;/p&gt;

&lt;p&gt;The data transformation function can be unit tested with ease, and can be refactored without impact on the state machine. The code for each lives in a distinct location and isolated context.&lt;/p&gt;

&lt;p&gt;The problem is much clearer.&lt;/p&gt;

&lt;p&gt;Once the state machine is in place (Array.filter), the design of the data transformation can be iterated on with an automated test suite with ease, leaving the programmer to focus on one thing only.&lt;/p&gt;

&lt;p&gt;A mistake in syntax while doing that can only break one thing. The programmer is not grappling with and changing code that affects two concerns at the same time.&lt;/p&gt;

&lt;p&gt;There is no mutation to track (another surface area for bugs).&lt;/p&gt;

&lt;p&gt;This is a microcosm, but I believe one that perfectly expresses as a koan the power and beauty of taking a vow to deliberately avoid using variables and loops.&lt;/p&gt;

&lt;p&gt;I will not overstep my bounds by making an evangelical claim of some absolute truth, and I invite you to try it. Program without variables and loops, and observe how it changes the way that the problems in front of you present themselves, and what solutions emerge from taking on this discipline.&lt;/p&gt;

</description>
      <category>stackoverflow</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Easily run multiple apps with HTTPS using Docker and LetsEncrypt</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Mon, 17 Feb 2020 07:01:43 +0000</pubDate>
      <link>https://forem.com/jwulf/easily-run-multiple-apps-with-https-using-docker-and-letsencrypt-ad8</link>
      <guid>https://forem.com/jwulf/easily-run-multiple-apps-with-https-using-docker-and-letsencrypt-ad8</guid>
      <description>&lt;p&gt;I frequently deploy Web APIs in Docker. On a single VM I might have up to five or six services running.&lt;/p&gt;

&lt;p&gt;All of them have TLS certificates and are accessible via port 443 using &lt;a href="https://www.cloudflare.com/learning/ssl/what-is-sni/"&gt;SNI&lt;/a&gt; — Server Name Indication, where the request is routed to the correct backend based on the domain name of the request.&lt;/p&gt;

&lt;p&gt;This is very easy to set up using &lt;a href="https://github.com/jwulf/letsencrypt-nginx-sidecar"&gt;letsencrypt-nginx-sidecar&lt;/a&gt;, (which builds on &lt;a href="https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion"&gt;this project&lt;/a&gt; and &lt;a href="https://github.com/jwilder/nginx-proxy"&gt;this project&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This approach is super-quick to set up and use, and reliable — I’ve been using it for a couple of years now, with no issues. It uses an nginx reverse proxy that listens to a Docker network. When a container joins the Docker network, the nginx reverse proxy adds an entry to route requests to it, and a companion container contacts LetsEncrypt to automatically provision a certificate for TLS connections to that domain.&lt;/p&gt;

&lt;p&gt;Setting it up is easy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Git clone the repo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone [https://github.com/jwulf/letsencrypt-nginx-sidecar.git](https://github.com/jwulf/letsencrypt-nginx-sidecar.git)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now create a Docker network for your containers:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create letsencrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Start the nginx proxy and LetsEncrypt companion:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd sidecar &amp;amp;&amp;amp; docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! You can now deploy multiple webapps to this host using docker-compose and have them automatically proxied with TLS.&lt;/p&gt;

&lt;p&gt;You have a proxy running that can route requests to your webapps based on the domain name of the request, and which will automatically get (and renew) a certificate for TLS as you add new webapps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adding a web app
&lt;/h4&gt;

&lt;p&gt;Any time you want to add another web app to this network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First set up the DNS record for the domain to point to the host machine’s external interface.&lt;/li&gt;
&lt;li&gt;In the docker-compose.yml file for the webapp, add the following environment variables:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;environment:
  # Set to the port that your webapp listens on
  VIRTUAL\_PORT=3000

  # Can be comma-separated list
  VIRTUAL\_HOST=mysubdomain.mydomain.com 

  # The domain for the cert, can also be comma-separated
  LETSENCRYPT\_HOST=mysubdomain.mydomain.com

  # Your email for the cert
  LETSENCRYPT\_EMAIL=me@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add a networks entry to have your webapp join the letsencrypt network:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;networks:
  default:
    external:
      name: letsencrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thats it!&lt;/p&gt;

&lt;p&gt;Your webapp container will join the network, and the proxy and companion containers will do the rest.&lt;/p&gt;

&lt;p&gt;Your webapp container does not need to map any ports in the docker-compose.yml file. The nginx proxy handles all external communication and routes requests over the Docker network.&lt;/p&gt;

&lt;p&gt;You can see an example of a complete docker-compose.yml file in &lt;a href="https://github.com/jwulf/camunda-cloud-demo-json-api/blob/master/deploy/docker-compose.yml"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>docker</category>
      <category>https</category>
    </item>
    <item>
      <title>Complex multi-repo builds with GitHub Actions and Camunda Cloud</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Wed, 12 Feb 2020 14:06:55 +0000</pubDate>
      <link>https://forem.com/jwulf/complex-multi-repo-builds-with-github-actions-and-camunda-cloud-5124</link>
      <guid>https://forem.com/jwulf/complex-multi-repo-builds-with-github-actions-and-camunda-cloud-5124</guid>
      <description>&lt;p&gt;&lt;em&gt;BLUF (Bottom-line Up-front): GitHub Actions are AWESOME and will change your life, but you risk losing yourself in a microservices architecture of repos, or have to go monolith once you get a few dependent projects or cross service provider boundaries — unless you orchestrate. I show you how I did it in this article.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tDcnoH1d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AGMkwLiJMp87BWpsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tDcnoH1d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AGMkwLiJMp87BWpsr.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get access to the Camunda Cloud Public Access Beta &lt;a href="https://accounts.cloud.camunda.io/signup"&gt;here&lt;/a&gt;. Use the &lt;a href="https://github.com/marketplace/actions/zeebe-action"&gt;Zeebe GitHub Action&lt;/a&gt; to orchestrate multi-repo builds with Zeebe and Camunda Cloud.&lt;/p&gt;

&lt;p&gt;In this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preamble&lt;/li&gt;
&lt;li&gt;What are GitHub Actions?&lt;/li&gt;
&lt;li&gt;GitHub Actions: The Good&lt;/li&gt;
&lt;li&gt;GitHub Actions: The Bad&lt;/li&gt;
&lt;li&gt;GitHub Actions: The Ugly&lt;/li&gt;
&lt;li&gt;Custom CI with GitHub Actions and Camunda Cloud&lt;/li&gt;
&lt;li&gt;Zeebe GitHub Action&lt;/li&gt;
&lt;li&gt;Modelling the problem / solution&lt;/li&gt;
&lt;li&gt;Starting a Camunda Cloud workflow from GitHub&lt;/li&gt;
&lt;li&gt;Passing around an encrypted GitHub token&lt;/li&gt;
&lt;li&gt;Setting up Camunda Cloud to access the GitHub API&lt;/li&gt;
&lt;li&gt;Passing in a secret from Worker Variables&lt;/li&gt;
&lt;li&gt;Using workflow variables as string constants&lt;/li&gt;
&lt;li&gt;Triggering a GitHub workflow from Camunda Cloud&lt;/li&gt;
&lt;li&gt;Problems accessing the GitHub API&lt;/li&gt;
&lt;li&gt;Communicating a GitHub Workflow outcome to Camunda Cloud&lt;/li&gt;
&lt;li&gt;GitHub Matrix builds&lt;/li&gt;
&lt;li&gt;Multiple causes for an action&lt;/li&gt;
&lt;li&gt;Specialising HTTP Worker payloads&lt;/li&gt;
&lt;li&gt;Specialising GitHub Workflows&lt;/li&gt;
&lt;li&gt;Ad Infinitum: To Infinity, and Beyond!&lt;/li&gt;
&lt;li&gt;Live Stream&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preamble
&lt;/h3&gt;

&lt;p&gt;Over the weekend, I did some work on an side-project, &lt;a href="https://www.magikcraft.io/"&gt;Magikcraft&lt;/a&gt;. It’s a platform for teaching kids to code in JavaScript, and it builds on an excellent Open Source project called &lt;a href="https://github.com/walterhiggins/ScriptCraft"&gt;ScriptCraft&lt;/a&gt;, written by Walter Higgins.&lt;/p&gt;

&lt;p&gt;Since Microsoft bought Minecraft, the pace of development has increased, and there have been a lot of breaking changes to the APIs. Oracle have also deprecated the JavaScript engine — Nashorn — that we have been using since JDK 8, and introduced their new polyglot engine GraalVM.&lt;/p&gt;

&lt;p&gt;Testing on and supporting all the various combinations of Minecraft server versions and JS runtimes has stretched the capacity of the ScriptCraft community, but there are many passionate users, including other downstream kids coding programs, like &lt;a href="https://codemakers.io/"&gt;Code Makers&lt;/a&gt;, that use it.&lt;/p&gt;

&lt;p&gt;It got to a breaking point with the latest Minecraft release, when a workaround patch we’d been relying on finally stopped working, and Walter let us all know that he was strapped for time and attention to give to the project. So I chipped in and built an automated building and testing infrastructure using &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are GitHub Actions?
&lt;/h3&gt;

&lt;p&gt;“GitHub Actions” is the overall name given to a workflow automation capability on GitHub. I suppose it is not called “GitHub Workflows” because that namespace is already taken by the git-flow kinda stuff.&lt;/p&gt;

&lt;p&gt;You have workflows, which you define in YAML files in a .github/workflows folder in your GitHub repo. These workflows contain named jobs, which are made up of 1 or more named steps. The steps can use GitHub Actions, which are reusable modules of functionality, providing capabilities like installing build chains and runtimes, executing Docker commands, and so forth.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions: The Good
&lt;/h3&gt;

&lt;p&gt;GitHub Actions let you define workflows in YAML that run in response to actions like a git push. You can use one of the many &lt;a href="https://github.com/marketplace?type=actions"&gt;existing actions&lt;/a&gt;, or &lt;a href="https://github.com/actions/typescript-action"&gt;build your own&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The workflows run on Azure — no surprise there, since Microsoft bought GitHub. What &lt;em&gt;was&lt;/em&gt; a surprise to me, however, is how &lt;em&gt;fast&lt;/em&gt; they run. I created a matrix build that builds and publishes nine — yes, 9 — Docker images to Docker Hub. The equivalent build on Docker Hub takes up to an hour. On GitHub, it is done in minutes. There are some serious compute resources available on there.&lt;/p&gt;

&lt;p&gt;It is so much faster that I moved the Docker image builds from Docker Hub’s automated builds to GitHub Actions using the &lt;a href="https://github.com/elgohr/Publish-Docker-Github-Action"&gt;Publish Docker GitHub Action&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions: The Bad
&lt;/h3&gt;

&lt;p&gt;You can’t validate or test your workflows without pushing them to GitHub. There is no local executor like there is for &lt;a href="https://circleci.com/docs/2.0/local-cli/"&gt;CircleCI&lt;/a&gt;, and while there is a &lt;a href="https://marketplace.visualstudio.com/items?itemName=me-dutour-mathieu.vscode-github-actions"&gt;validator plugin for VS Code&lt;/a&gt;, I couldn’t get it to give me “required parameter” feedback for the &lt;a href="https://github.com/marketplace/actions/zeebe-action"&gt;Zeebe GitHub Action&lt;/a&gt; that I created.&lt;/p&gt;

&lt;p&gt;This is offset by how fast GitHub Actions run, so the code-execute-debug cycle is bearable. If it weren’t this fast, that would be a serious problem — but I actually enjoyed the power of so much compute resources dedicated to Open Source while debugging, so it kept me going.&lt;/p&gt;

&lt;p&gt;YAML — meh, as one product owner told me over dinner at the recent DevConf.CZ: “&lt;em&gt;It’s the least worst thing we’ve come up with so far&lt;/em&gt;”, paraphrasing Churchill’s take on democracy. Of course, I was like: “&lt;em&gt;Have you heard of this thing called BPMN?&lt;/em&gt;“&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sidenote&lt;/em&gt;: when I started teaching kids to code, many were using this thing called &lt;a href="https://scratch.mit.edu/"&gt;Scratch&lt;/a&gt;, which is a block-based programming language. I turned my nose up at it, saying: “&lt;em&gt;That’s not real programming&lt;/em&gt;”, and proceded to teach them the difference between ) and } and where to find them on the keyboard - like that’s real programming. My “Road to Damascus” moment was when an 8-year old kid came in and I coached him through making a combination lock guessing game, and he built a working prototype &lt;em&gt;in thirty minutes&lt;/em&gt; with Scratch - because he was dealing purely with the logic of the program, and not struggling with syntax errors. The trickiest thing for him was state management, not balancing the right type of brackets.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions: The Ugly
&lt;/h3&gt;

&lt;p&gt;Part of my contribution upstream to ScriptCraft has been integration with &lt;a href="https://www.npmjs.com/"&gt;NPM&lt;/a&gt;, the JavaScript packaging ecosystem. The purpose of that is to unlock the innovation of the community to create, share, and discover others’ work — an essential dynamic in a vibrant Open Source community.&lt;/p&gt;

&lt;p&gt;So, once I got automated builds, unit testing JavaScript in a dockerized Minecraft server in a GitHub action (seriously — your workflow can run for up to &lt;em&gt;six hours&lt;/em&gt; — you could &lt;em&gt;play&lt;/em&gt; Minecraft in a GitHub action), and publishing test images to Docker Hub, the obvious next question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how do I trigger a test run for downstream dependent packages when I publish a new image of the core API?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, you can &lt;a href="https://github.community/t5/GitHub-Actions/Triggering-by-other-repository/td-p/30668"&gt;trigger GitHub actions in a downstream repository&lt;/a&gt; via webhook configuration (there is even &lt;a href="https://github.com/marketplace/actions/repository-dispatch"&gt;a GitHub Action for it&lt;/a&gt;), but now you enter the rabbit hole of peer-to-peer choreography, with an attendant loss of visibility.&lt;/p&gt;

&lt;p&gt;Is everything wired up? When you walk away from it for a week or two, or a month, and come back — how do you remember how it is all hooked up? Where do you go for the single point of truth for the system?&lt;/p&gt;

&lt;p&gt;Even with three layers (base image, release package, image with release), and one dependent package, I’m having trouble remembering what triggers what and how.&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;I push to this GitHub repo, that triggers Docker Hub, when that is done it should trigger a retest and rebuild of this other layer on GitHub, and that should then publish to Docker Hub, which should trigger all downstream package tests.&lt;/em&gt;” Pretty straight-forward, no? No.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Magikcraft/MagikCraft/wiki"&gt;Documentation&lt;/a&gt; is one attempt to address this — but is that really going to stay up to date?&lt;/p&gt;

&lt;p&gt;Devolving responsibility is another way — package maintainers are responsible for triggering their own rebuilds / retesting. But again, there is a tight coupling to automate that, and it is opaque. And I already know that when I walk away from the small part that is already implemented, I will forget how it works and curse the idiot who wrote the documentation because it doesn’t make sense.&lt;/p&gt;

&lt;p&gt;This is not a problem with GitHub Actions really, it is a characteristic of any complex system with multiple stakeholders, integrating disparate systems, with limited resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom CI with GitHub Actions and Camunda Cloud
&lt;/h3&gt;

&lt;p&gt;It was right about then — 2am in the morning — that I remembered Ruslan’s recent article “&lt;a href="https://medium.com/@ruslanfg/quick-serverless-start-to-camunda-cloud-public-beta-ea90e8e70e5d"&gt;Quick Serverless start to Camunda Cloud Public Beta&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;In the list of example use cases at the start he included “&lt;em&gt;Custom CI/CD&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;Now, I always thought the “custom CI” example was contrived — I mean, isn’t that what CI systems &lt;em&gt;literally do&lt;/em&gt;? Don’t they have this stuff built-in?&lt;/p&gt;

&lt;p&gt;Spoiler: Yes, they do — but often using peer-to-peer choreography, and without a dependency graph — especially when you start to scale across a community or an ecosystem, or even just multiple service providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zeebe GitHub Action
&lt;/h3&gt;

&lt;p&gt;To solve this multi-repo build automation with Camunda Cloud, I wrote a &lt;a href="https://github.com/jwulf/zeebe-action#start-a-workflow"&gt;Zeebe GitHub Action&lt;/a&gt;. It bundles the &lt;a href="https://github.com/creditsenseau/zeebe-client-node-js"&gt;Zeebe Node client&lt;/a&gt; into a single file, and allows you to use it with declarative YAML in GitHub workflows to create workflow instances and publish messages back to Camunda Cloud from within GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelling the problem / solution
&lt;/h3&gt;

&lt;p&gt;OK, so let’s do this.&lt;/p&gt;

&lt;p&gt;We going to create executable documentation of the system architecture that &lt;strong&gt;cannot&lt;/strong&gt; go out of date, and crystallise it with the minimum number of moving parts to scale to more dependent packages.&lt;/p&gt;

&lt;p&gt;We can use the &lt;a href="https://developer.github.com/v3/actions/workflows/"&gt;GitHub API v3&lt;/a&gt; — it’s an old school REST API left over from the days before &lt;a href="https://developer.github.com/v4/"&gt;GraphQL&lt;/a&gt;. It will give the whole thing a retro vibe, and we can call it directly from Camunda Cloud using the built-in HTTP Worker. I’m joking! We’re going to build a custom GraphQL client. No, we are not. REST is fine, and perfectly suited for this task. We are not requesting arbitrary related data over multiple calls, or introspecting an unknown schema, so GraphQL has no benefit here.&lt;/p&gt;

&lt;p&gt;We can trigger a GitHub workflow over the API with a &lt;a href="https://developer.github.com/v3/repos/#create-a-repository-dispatch-event"&gt;repository dispatch&lt;/a&gt; event.&lt;/p&gt;

&lt;p&gt;First step, I model how I want the system to behave in the &lt;a href="https://github.com/zeebe-io/zeebe-modeler"&gt;Zeebe Modeler&lt;/a&gt; — a desktop application for creating BPMN diagrams (&lt;em&gt;apparently it is XML in the background, but seriously — it’s 2020, who needs to see that stuff?&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NuvURMo9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AC3Shx1thAum4A-wM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NuvURMo9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AC3Shx1thAum4A-wM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are three dependent repositories that should trigger a rebuild / test in all downstream repos when they are updated.&lt;/p&gt;

&lt;p&gt;The first one gets triggered in response to a push to a git repo, and has no other cause. This repo has a GitHub workflow that builds and pushes the base images to Docker Hub. When this GitHub workflow is complete, it communicates this back to Camunda Cloud by &lt;a href="https://github.com/jwulf/zeebe-action#publish-a-message"&gt;using the Zeebe GitHub Action to publish a message to Camunda Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yiLm2BN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AblDciayDWEuiCvmD.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yiLm2BN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AblDciayDWEuiCvmD.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting a Camunda Cloud workflow from GitHub
&lt;/h3&gt;

&lt;p&gt;We need to correlate this message back to the running process instance, so we need a unique correlation id. We can get one by starting the Camunda Cloud workflow from GitHub on repo push &lt;a href="https://github.com/jwulf/zeebe-action#start-a-workflow"&gt;using the Zeebe GitHub Action to create a workflow instance&lt;/a&gt;. When we start it, we’ll pass in a unique buildid, which we will use to correlate messages to this particular workflow run (see &lt;a href="https://zeebe.io/blog/2019/08/zeebe-message-correlation/"&gt;this article&lt;/a&gt; for a great tutorial on message correlation in Zeebe).&lt;/p&gt;

&lt;p&gt;Here is the GitHub workflow that is triggered by a push to the repo, and starts a workflow instance in Camunda Cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Kick off Build Orchestration

on:
  push:
    branches:
      - "master"

jobs:
  startWorkflow:
    runs-on: ubuntu-latest
    steps:
      - name: Get current time
        uses: gerred/actions/current-time@master
        id: current-time
      - name: Create Zeebe Workflow
        uses: jwulf/zeebe-action@master
        with:
          zeebe\_address: ${{ secrets.ZEEBE\_ADDRESS }}
          zeebe\_client\_id: ${{ secrets.ZEEBE\_CLIENT\_ID }}
          zeebe\_authorization\_server\_url: ${{ secrets.ZEEBE\_AUTHORIZATION\_SERVER\_URL }}
          zeebe\_client\_secret: ${{ secrets.ZEEBE\_CLIENT\_SECRET }}
          operation: createWorkflowInstance
          bpmn\_process\_id: magikcraft-github-build
          variables: '{"buildid": "${{ github.sha }}-${{ steps.current-time.outputs.time }}", "gitHubToken": "Bearer {{GitHubToken}}", "TYPE\_TEST": "TYPE\_TEST", "TYPE\_PUBLISH": "TYPE\_PUBLISH" }'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The ZEEBE secrets come from the Camunda Cloud console client configuration, and are &lt;a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets"&gt;added to the GitHub repo as secrets&lt;/a&gt;, so that the GitHub Actions have access to them in the &lt;a href="https://help.github.com/en/actions/reference/contexts-and-expression-syntax-for-github-actions#contexts"&gt;secrets context&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing around an encrypted GitHub token
&lt;/h3&gt;

&lt;p&gt;We also need to get our GitHub Token into the workflow. We could put it into the variables of the Camunda Cloud workflow when we create it from the GitHub workflow (it is injected there as $&lt;code&gt;{{ secrets.GITHUB_TOKEN }}&lt;/code&gt;). However, this will put it into the Camunda Cloud workflow variables in plaintext - which we would like to avoid if we can.&lt;/p&gt;

&lt;p&gt;Luckily, we can. The Camunda Cloud HTTP Worker can store encrypted secrets and replace them at run-time from a template. So we can &lt;a href="https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line"&gt;create a personal token on GitHub&lt;/a&gt; (make sure it has the repo scope). Then, add a GitHubToken secret in the HTTP Worker “Worker Variables” dialog in the Camunda Cloud Console, and put the GitHub Token in there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Camunda Cloud to access the GitHub API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Log into the &lt;a href="https://console.cloud.camunda.io/"&gt;Camunda Cloud console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Go to your cluster, and click on “Worker Variables”. These are like secrets in CI systems — you can inject them by name, rather than putting secrets in your BPMN or code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3lmkzbdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2ARXyZouPk0a9si1cX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3lmkzbdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2ARXyZouPk0a9si1cX.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on “Add Variable”, and create a new variable called GitHubToken. Paste the token in there as the value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DUUs1N_V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/974/0%2AVBPwL2yKkgl-OeRT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUUs1N_V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/974/0%2AVBPwL2yKkgl-OeRT.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing in a secret from Worker Variables
&lt;/h3&gt;

&lt;p&gt;When we pass in our authorization header that will use the secret Worker Variable, it looks like this in the variables payload for a createWorkflowInstance or publishMessage call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  GitHubToken: "Bearer {{GitHubToken}}"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The Bearer part is important, so don’t forget it. The &lt;code&gt;{{GitHubToken}}&lt;/code&gt; part will be templated with the value from the Worker Variables when the HTTP Worker uses this variable. I should have called this variable GitHubAuthHeader to make it clear what it is, and how it is distinct from the Worker Variable.&lt;/p&gt;

&lt;p&gt;So you can think of it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  GitHubAuthHeader: "Bearer {{GitHubToken}}"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The workflow’s GitHubAuthHeader variable now contains an auth header, and the HTTP Worker will template in the value of the GitHubToken secret from the Worker Variables.&lt;/p&gt;

&lt;p&gt;So the on push workflow in the base image repo does nothing more than create a new workflow instance in Camunda Cloud, with a unique buildid for this instance, and an authorization header for our GitHub API calls, containing the name of the encrypted secret to template in at runtime, and some string constants to allow us to specialise HTTP payloads via I/O mapping. What??&lt;/p&gt;

&lt;h3&gt;
  
  
  Using workflow variables as string constants
&lt;/h3&gt;

&lt;p&gt;We create the Camunda Cloud workflow instance with some variables that will act as string constants for us in the workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  TYPE\_TEST: "TYPE\_TEST", 
  TYPE\_PUBLISH: "TYPE\_PUBLISH" 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We don’t have the ability to specialise the payload of HTTP Worker calls with string constants in a BPMN model — but we &lt;em&gt;can&lt;/em&gt; through &lt;em&gt;variable mapping&lt;/em&gt; if we start the workflow with a known “enum” dictionary. Then we can specify a string constant by mapping one of these variables into the body.&lt;/p&gt;

&lt;p&gt;There is always a way.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://github.com/Magikcraft/minecraft/blob/master/.github/workflows/onpush.yml"&gt;view this initial workflow on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggering a GitHub workflow from Camunda Cloud
&lt;/h3&gt;

&lt;p&gt;The first thing our Camunda Cloud workflow does is trigger another GitHub workflow in the base image repo, to rebuild and publish the base images to Docker Hub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nwo-3Mm6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/454/0%2AbxGAeyKeXWOPMBQe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nwo-3Mm6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/454/0%2AbxGAeyKeXWOPMBQe.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We trigger the workflow by posting a repository_dispatch event to the base image repo via the GitHub API, using the Camunda Cloud HTTP Worker.&lt;/p&gt;

&lt;p&gt;The service task that does this has the task type CAMUNDA-HTTP.&lt;/p&gt;

&lt;p&gt;It has two custom headers that are semantic for this worker: method and url.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u9Ng0OJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/764/0%2AasF6zTD569odtK2Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u9Ng0OJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/764/0%2AasF6zTD569odtK2Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We set method to post for an HTTP POST request, and set the url to &lt;a href="https://api.github.com/repos/Magikcraft/minecraft/dispatches"&gt;https://api.github.com/repos/Magikcraft/minecraft/dispatches&lt;/a&gt; for the &lt;a href="https://developer.github.com/v3/repos/#create-a-repository-dispatch-event"&gt;repository dispatch event&lt;/a&gt; endpoint for this repo.&lt;/p&gt;

&lt;p&gt;Two of the workflow &lt;em&gt;variables&lt;/em&gt; are also semantic for the HTTP worker: body and authorization.&lt;/p&gt;

&lt;p&gt;The value of body will be JSON parsed and sent as the body of the HTTP request, and the value of authorization will be sent as the authorization header.&lt;/p&gt;

&lt;p&gt;So we create I/O mappings in the Zeebe modeler to map the variable gitHubToken (which contains our templated auth header string with GitHub token) to authorization, and the buildid (our unique correlation id) to body.client_payload.&lt;/p&gt;

&lt;p&gt;The body.client_payload key can contain arbitrary data that will be available to the GitHub Actions that respond to this event. With access to the correlation key buildid, they can publish messages back to Camunda Cloud that are correlated to this running workflow instance.&lt;/p&gt;

&lt;p&gt;The body.event_type key is required by the GitHub API, and here we can map one of our enum variables in: TYPE_PUBLISH. This repo does not have specialised behaviours that we need to orchestrate, so we don’t bother testing the event_type in the triggered workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wsVYvpep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/784/0%2Anv8LLAtat86Mz-ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wsVYvpep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/784/0%2Anv8LLAtat86Mz-ng.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems accessing the GitHub API
&lt;/h3&gt;

&lt;p&gt;If GitHub is responding with 404, if the URL is correct (double-check it!) — it means that you were not authenticated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Typically, we send a 404 error when your client isn’t properly authenticated. You might expect to see a 403 Forbidden in these cases. However, since we don’t want to provide any information about private repositories, the API returns a 404 error instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;from &lt;a href="https://developer.github.com/v3/troubleshooting/"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can debug this by pointing the HTTP Worker at &lt;a href="https://webhook.site/"&gt;https://webhook.site/&lt;/a&gt; to see the exact payload it is sending to the GitHub API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communicating a GitHub Workflow outcome to Camunda Cloud
&lt;/h3&gt;

&lt;p&gt;We can publish a message to Camunda Cloud from the GitHub workflow after we push the base images to Docker Hub.&lt;/p&gt;

&lt;p&gt;Here is the section of the base image repo’s publish workflow that communicates success back to Camunda Cloud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notify\_Camunda\_Cloud:
    runs-on: ubuntu-latest
    needs: build\_and\_publish
    steps:
      - name: Tell Camunda Cloud What's up!
        uses: jwulf/zeebe-action@0.2.0
        with:
          zeebe\_address: ${{ secrets.ZEEBE\_ADDRESS }}
          zeebe\_client\_id: ${{ secrets.ZEEBE\_CLIENT\_ID }}
          zeebe\_authorization\_server\_url: ${{ secrets.ZEEBE\_AUTHORIZATION\_SERVER\_URL }}
          zeebe\_client\_secret: ${{ secrets.ZEEBE\_CLIENT\_SECRET }}
          operation: publishMessage
          message\_name: BASE\_IMAGE\_REBUILT
          correlationKey: ${{ github.event.client\_payload.buildid }}
          variables: '{"test\_passed": "false"}'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://github.com/Magikcraft/minecraft/blob/master/.github/workflows/dockerimage.yml"&gt;View it on GitHub&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However, execution halts in the GitHub workflow if the task fails. One way to deal with failure is to &lt;a href="https://github.community/t5/GitHub-Actions/Github-Actions-trigger-workflow-on-Succes-Failure-of-other/m-p/17944"&gt;wrap every step in a GitHub workflow and test for failure&lt;/a&gt;, but that is clunky.&lt;/p&gt;

&lt;p&gt;Instead, we will use a boundary interrupting timer to detect failure by timing it out. We could publish a message from somewhere else by listening to the repo webhooks, but we want to do this with zero additional infrastructure.&lt;/p&gt;

&lt;p&gt;Our timeout task could trigger any notification behaviour, but let’s keep it zero-infra and raise an incident in Operate. We can do this by creating a CAMUNDA-HTTP task with an I/O mapping that we know does not exist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2dZkEUHi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AwJnBE6twlL92-DIY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2dZkEUHi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AwJnBE6twlL92-DIY.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the I/O mapping we map build_timed_out_set_true_to_retry to dummy. When the token enters this task, it will raise an incident because the variable build_timed_out_set_true_to_retry doesn’t exist. When you fix the issue with the build, you can set this variable to true in Operate and retry.&lt;/p&gt;

&lt;p&gt;The I/O mapping for the task maps the build_timed_out_set_true_to_retry variable to dummy on output, so we can reuse this pattern elsewhere - the variable is still unset in the workflow.&lt;/p&gt;

&lt;p&gt;This CAMUNDA-HTTP task, when it does run, just does a GET to Google. If that fails, then you have bigger problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Matrix builds
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.edwardthomson.com/blog/github_actions_2_matrixes.html"&gt;Matrix builds&lt;/a&gt; in GitHub workflows allow you to build or test multiple configurations with the same steps. By default the workflow halts on the first failure, but you can specify fail-fast: false to have the build execute all combinations before announcing failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Publish Docker images

on: [repository\_dispatch]

jobs:
  build\_and\_publish:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        baseimage:
          - { image: "oracle/graalvm-ce:19.3.1-java8", tag: graalvm }
          - { image: "openjdk:8u171-jdk-alpine3.8", tag: openjdk8 }
        minecraft:
          - { jar: paper-1.15.2, dockerfile: Dockerfile }
          - { jar: paper-1.14.4, dockerfile: Dockerfile }
          - { jar: nukkit-1.0, dockerfile: Dockerfile-nukkit }
          - { jar: nukkit-2.0, dockerfile: Dockerfile-nukkit }
    steps:
      - uses: actions/checkout@v2
      - name: Publish Docker image to Registry
        uses: elgohr/Publish-Docker-Github-Action@2.12
        env:
          BASEIMAGE: ${{ matrix.baseimage.image }}
          MINECRAFT\_JAR: ${{ matrix.minecraft.jar }}.jar
        with:
          name: magikcraft/minecraft
          username: ${{ secrets.DOCKER\_USERNAME }}
          password: ${{ secrets.DOCKER\_PASSWORD }}
          dockerfile: Dockerfile
          tags: ${{ matrix.minecraft.jar }}-${{ matrix.baseimage.tag }}

  notify\_Camunda\_Cloud:
    runs-on: ubuntu-latest
    needs: build\_and\_publish
    steps:
      - name: Tell Camunda Cloud What's up!
        uses: jwulf/zeebe-action@0.2.0
        with:
          zeebe\_address: ${{ secrets.ZEEBE\_ADDRESS }}
          zeebe\_client\_id: ${{ secrets.ZEEBE\_CLIENT\_ID }}
          zeebe\_authorization\_server\_url: ${{ secrets.ZEEBE\_AUTHORIZATION\_SERVER\_URL }}
          zeebe\_client\_secret: ${{ secrets.ZEEBE\_CLIENT\_SECRET }}
          operation: publishMessage
          message\_name: BASE\_IMAGE\_REBUILT
          correlationKey: ${{ github.event.client\_payload.buildid }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We make the second step (_notify_Camunda_Cloud_) — where we publish the success message to Camunda Cloud, depend on the first step (_build_and_publish_) — the one where we publish the Docker images.&lt;/p&gt;

&lt;p&gt;We do this by specifying needs: build_and_publish in the second step. This prevents GitHub from parallelizing the tasks and optimistically reporting success back to our workflow in Camunda Cloud.&lt;/p&gt;

&lt;p&gt;This ends the first repository’s automation. If this succeeds, then our dependent repository needs to retest, rebuild and publish Docker images, then trigger further dependents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple causes for an action
&lt;/h3&gt;

&lt;p&gt;Our second repository can be rebuilt in response to two different events:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A git push to its own code.&lt;/li&gt;
&lt;li&gt;A rebuild of the base images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mewYyph_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A3aBzwfwLHazWbUhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mewYyph_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A3aBzwfwLHazWbUhk.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We model this by creating a message start event, and then in the &lt;a href="https://github.com/Magikcraft/MagikCraft/blob/development/.github/workflows/trigger-workflow.yml"&gt;on push workflow in the repository&lt;/a&gt;, we publish this message with the metadata that our workflow needs — the templated authorization header to get our GitHub token injected into the HTTP Worker, a unique buildid, and the string constants.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Test

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Get current time
        uses: gerred/actions/current-time@master
        id: current-time
      - name: Kick off workflow
        uses: jwulf/zeebe-action@0.2.0
        with:
          zeebe\_address: ${{ secrets.ZEEBE\_ADDRESS }}
          zeebe\_client\_id: ${{ secrets.ZEEBE\_CLIENT\_ID }}
          zeebe\_authorization\_server\_url: ${{ secrets.ZEEBE\_AUTHORIZATION\_SERVER\_URL }}
          zeebe\_client\_secret: ${{ secrets.ZEEBE\_CLIENT\_SECRET }}
          operation: publishMessage
          message\_name: MAGIKCRAFT\_API\_PUSH
          variables: '{"buildid": "${{ github.sha }}-${{ steps.current-time.outputs.time }}", "gitHubToken": "Bearer {{GitHubToken}}", , "TYPE\_TEST": "TYPE\_TEST", "TYPE\_PUBLISH": "TYPE\_PUBLISH"}'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From that point forward, the workflow in Camunda Cloud is identical, regardless of how we got here.&lt;/p&gt;

&lt;p&gt;We use the same timeout pattern to detect failure, and run a matrix test in &lt;a href="https://github.com/Magikcraft/MagikCraft/blob/development/.github/workflows/test.yml"&gt;the next workflow we trigger from the Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specialising HTTP Worker payloads
&lt;/h3&gt;

&lt;p&gt;In this subprocess we have &lt;em&gt;two&lt;/em&gt; distinct GitHub workflows in the same repo that we are triggering from Camunda Cloud using the repository dispatch event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4_nJ9jD4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AhltzbrFi60utB1xy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4_nJ9jD4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AhltzbrFi60utB1xy.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our tests may fail on the new base images, or due to the new code we just pushed, so we don’t want to publish test containers if it does. So we have a test workflow and a publish workflow.&lt;/p&gt;

&lt;p&gt;To get specialised behaviour in response to the repository dispatch event, we need to specialise the HTTP Worker task that calls the GitHub API. We do this by mapping one of the string constants from our enum dictionary in the variables to body.event_type in the I/O Mappings for the task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;bpmn:serviceTask id="Task\_0nvbnfg" name="Trigger matrix tests with base images"&amp;gt;
  &amp;lt;bpmn:extensionElements&amp;gt;
    &amp;lt;zeebe:taskDefinition type="CAMUNDA-HTTP" /&amp;gt;
    &amp;lt;zeebe:ioMapping&amp;gt;
      &amp;lt;zeebe:input source="gitHubToken" target="authorization" /&amp;gt;
      &amp;lt;zeebe:input source="buildid" target="body.client\_payload.buildid" /&amp;gt;
      &amp;lt;zeebe:input source="TYPE\_TEST" target="body.event\_type" /&amp;gt;
      &amp;lt;zeebe:input source="test\_passed" target="body.client\_payload.test\_passed" /&amp;gt;
    &amp;lt;/zeebe:ioMapping&amp;gt;
    &amp;lt;zeebe:taskHeaders&amp;gt;
      &amp;lt;zeebe:header key="method" value="post" /&amp;gt;
      &amp;lt;zeebe:header key="url" value="https://api.github.com/repos/Magikcraft/MagikCraft/dispatches" /&amp;gt;
    &amp;lt;/zeebe:taskHeaders&amp;gt;
  &amp;lt;/bpmn:extensionElements&amp;gt;
  &amp;lt;bpmn:incoming&amp;gt;SequenceFlow\_0weo0p1&amp;lt;/bpmn:incoming&amp;gt;
  &amp;lt;bpmn:outgoing&amp;gt;SequenceFlow\_0a48581&amp;lt;/bpmn:outgoing&amp;gt;
&amp;lt;/bpmn:serviceTask&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Specialising GitHub Workflows
&lt;/h3&gt;

&lt;p&gt;We have two GitHub workflows that are triggered from the repository dispatch event. We can specialise the event_type that we send with the event from Camunda Cloud with the HTTP Worker, now we need to specialise the GitHub workflows.&lt;/p&gt;

&lt;p&gt;We can do this with &lt;a href="https://www.edwardthomson.com/blog/github_actions_13_conditionals.html"&gt;a conditional&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  test:
    if: github.event.action == 'TYPE\_TEST'
    runs-on: ubuntu-latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;event_type&lt;/code&gt; from the API call appears in the GitHub workflow as &lt;code&gt;github.event.action&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can debug &lt;code&gt;repository_dispatch&lt;/code&gt; events sent to a repository by creating a workflow like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Debug

on:
  - repository_dispatch
jobs:
  echo:
    runs-on: ubuntu-latest
    steps:
      - name: Debug
        run: echo "${{ toJson(github.event) }}"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;View the two workflows on GitHub: &lt;a href="https://github.com/Magikcraft/MagikCraft/blob/development/.github/workflows/test.yml"&gt;test&lt;/a&gt; and &lt;a href="https://github.com/Magikcraft/MagikCraft/blob/development/.github/workflows/release-test.yml"&gt;release-test&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ad Infinitum: To Infinity, and Beyond!
&lt;/h3&gt;

&lt;p&gt;The third subprocess is essentially the same thing repeated. To run unit tests inside Docker inside a GitHub Action, I took inspiration from this article: &lt;a href="https://medium.com/8fit-tech-blog/github-actions-running-javascript-unit-tests-on-docker-20e2426ddc1"&gt;Github actions running javascript unit tests on docker&lt;/a&gt;. The Magikcraft and plugin unit tests actually run in JavaScript inside Minecraft inside Docker inside a GitHub Action — now orchestrated by Camunda Cloud.&lt;/p&gt;

&lt;p&gt;It’s possible to extend this system further in either direction — for example, the base images are actually built in response to upstream dependencies updating, principally Minecraft server versions. That could be automated and added as a trigger to the whole process.&lt;/p&gt;

&lt;p&gt;One thing that became apparent to me is the need to use a standard pattern, and as thin a shim as possible at each layer.&lt;/p&gt;

&lt;p&gt;I discovered the idea of passing in string constants to specialise HTTP Worker payloads when I found myself replying to Camunda Cloud with a message that encoded knowledge in the GitHub workflow of where it was in the BPMN process flow — a clear symptom of tight coupling.&lt;/p&gt;

&lt;p&gt;The Zeebe Action came about in order to lift a repeated pattern to a first-class concern — I started by creating a light-weight &lt;a href="https://hub.docker.com/r/sitapati/zbctl"&gt;zbctl container&lt;/a&gt; and scripting it via BASH in GitHub Actions.&lt;/p&gt;

&lt;p&gt;There is more lifting that can be done, and I am sure that as patterns emerge there will be less manual wiring of I/O mappings that will be required to accomplish this integration.&lt;/p&gt;

&lt;p&gt;In the meantime, you can accomplish quite a lot with zero-infrastructure.&lt;/p&gt;

&lt;p&gt;An important feature of designing systems like this is in their comprehensibility and responsiveness to change — their agility. It is not just “&lt;em&gt;how easy is it to do?&lt;/em&gt;” it is “&lt;em&gt;how easy is it to extend and modify the behaviour of the resulting system?&lt;/em&gt;“&lt;/p&gt;

&lt;p&gt;For that, right now, I don’t have an answer. My intuition is that now that the basic components are in place and some patterns have emerged, it will be a flexible system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Stream
&lt;/h3&gt;

&lt;p&gt;Finally, here is a live stream of me working on this.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/HOjWTPKh0Lc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zeebe.io/what-is-zeebe/"&gt;&lt;strong&gt;What is Zeebe?&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>workflow</category>
      <category>continuousintegrati</category>
    </item>
    <item>
      <title>Beginners Guide to Live Streaming Code on Twitch in 2020</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Wed, 05 Feb 2020 16:19:42 +0000</pubDate>
      <link>https://forem.com/jwulf/beginners-guide-to-live-streaming-code-on-twitch-in-2020-2cf4</link>
      <guid>https://forem.com/jwulf/beginners-guide-to-live-streaming-code-on-twitch-in-2020-2cf4</guid>
      <description>&lt;p&gt;I started live streaming code on Twitch last year, and at Linux Conf AU in January I gave a talk about live streaming “How to live stream on Twitch and YouTube” that was live streamed on my &lt;a href="https://www.twitch.tv/thelegendaryjoshwulf"&gt;Twitch.tv&lt;/a&gt; channel.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/T3gMT-rDNEE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In this talk, I cover a lot of the mechanics and technology of streaming: the software (OBS / Ecamm Live), streaming platforms, microphones, control surfaces, lighting, and using a green screen. I show both ends of the spectrum: the ultra-high end and the “get started now in your garage”.&lt;/p&gt;

&lt;p&gt;Check out the archives of some of my live coding sessions in the archive in &lt;a href="https://www.youtube.com/playlist?list=PLn1M3BHFMIoPgmZ4XKIJtvX3kVapI0a-p"&gt;this YouTube playlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you stream, let me know. I love checking out other people’s streams.&lt;a href="https://www.freecodecamp.org/news/lessons-from-my-first-year-of-live-coding-on-twitch-41a32e2f41c1/"&gt;Suz Hinton&lt;/a&gt; (noopkat) and &lt;a href="https://twitter.com/mpjme?lang=en"&gt;MPJ&lt;/a&gt; inspired me to start streaming, and if this helps someone else get started, then I’d consider that paying it forward.&lt;/p&gt;

</description>
      <category>livestreaming</category>
      <category>tutorial</category>
      <category>twitch</category>
    </item>
    <item>
      <title>Microservices Operational Monitoring: Zeebe Cloud Canary</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Sun, 03 Nov 2019 12:04:23 +0000</pubDate>
      <link>https://forem.com/jwulf/microservices-operational-monitoring-zeebe-cloud-canary-174c</link>
      <guid>https://forem.com/jwulf/microservices-operational-monitoring-zeebe-cloud-canary-174c</guid>
      <description>&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://zeebe.io/blog/"&gt;Zeebe blog&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Designing a resilient microservices system means planning for, and alerting on various failure states. The &lt;a href="https://www.npmjs.com/package/zeebe-cloud-canary"&gt;Zeebe Cloud Canary&lt;/a&gt; npm package adds alerting to your Node.js &lt;a href="https://zeebe.io"&gt;Zeebe&lt;/a&gt; applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nADMrQgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2hsobz6ovkuu7b5724a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nADMrQgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2hsobz6ovkuu7b5724a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few things that can go wrong in a Zeebe system that you definitely want to surface operationally. Your client applications might exception and halt. The broker might fail - whether due to a hardware failure or some edge-case condition that puts it in an infinite restart loop while recovering (it could be memory constrained, for example, and rescheduled by K8s before it can recover its state on boot up).&lt;/p&gt;

&lt;p&gt;Both of these cases can be detected by probes. The broker has a &lt;a href="https://docs.zeebe.io/operations/network-ports.html?highlight=readiness#network-ports"&gt;readiness probe&lt;/a&gt; that can be monitored for this, and your application can have a periodic healthcheck using something like &lt;a href="https://healthchecks.io"&gt;healthchecks.io&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Another case that is more subtle: when the broker is running, and your application is too - but does not have a connection to the broker. Maybe something has failed in the network. With software-defined networking, it no longer takes someone removing the cap at the end of a 10-base-T network, or unplugging a workstation in the middle of a Token Ring network to disrupt a connection. &lt;/p&gt;

&lt;p&gt;In a development environment, for example, if you are forwarding ports to a broker in a Kubernetes cluster (maybe using &lt;a href="https://github.com/txn2/kubefwd"&gt;bulk kubefwd&lt;/a&gt;), the forwarding may stop. &lt;/p&gt;

&lt;p&gt;In this case, unless you are watching the logs, you may not notice that your application has lost its connection. It just looks like there is no work at the moment.&lt;/p&gt;

&lt;p&gt;The Node.js client does transparent client-side retries by default, and if you don't write solid handling on the &lt;code&gt;onDisconnect()&lt;/code&gt; handler, it will just keep trying to reconnect, and your application will report that it is alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Canary
&lt;/h2&gt;

&lt;p&gt;I've written an npm package &lt;a href="https://www.npmjs.com/package/zeebe-cloud-canary"&gt;&lt;code&gt;zeebe-cloud-canary&lt;/code&gt;&lt;/a&gt;, that deploys a canary workflow that chirps periodically. The canary worker pings a "chirp" endpoint whenever it gets the chirp task, and if it misses a chirp task by 50% of the heartbeat period, it can optionally ping a "squawk" endpoint.&lt;/p&gt;

&lt;p&gt;If you are using healthchecks.io, then you don't need a squawk endpoint, because healthchecks.io can be configured to alert you after a missing ping.&lt;/p&gt;

&lt;p&gt;In the initial implementation of this, I created a single, long-running workflow instance for the canary. This is problematic, because the workflow events are not reaped until the workflow completes. This causes disk space usage to increase over time, and broker recovery takes longer when a node is restarted (which can lead to those rebooting loops).&lt;/p&gt;

&lt;p&gt;The new implementation starts a new workflow instance for each chirp, from the canary worker that chirps. Message correlation is used to make sure that you get only a single chirp, and not a chorus of tweets. &lt;/p&gt;

&lt;p&gt;You can use this across multiple workers to prove that you have at least worker / application connected for the class. &lt;/p&gt;

&lt;h2&gt;
  
  
  Installing
&lt;/h2&gt;

&lt;p&gt;To install the package to your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i zeebe-cloud-canary
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Then in your application code, create a canary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZeebeCanary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zeebe-cloud-canary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Uses the zeebe-node zero-conf constructor, either localhost or from ENV&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ZeebeCanary&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ChirpUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;healthchecks_io_url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CanaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-canary-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;HeartbeatPeriodSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/jwulf/zeebe-cloud-canary/blob/master/README.md"&gt;README&lt;/a&gt; for more configuration options, and take a look at &lt;a href="https://github.com/jwulf/zeebe-cloud-canary/blob/master/src/ZeebeCanary.ts"&gt;the canary source code&lt;/a&gt; (it's just 108 lines).&lt;/p&gt;

&lt;p&gt;The canary uses &lt;a href="https://www.npmjs.com/package/micromustache"&gt;micromustache&lt;/a&gt; to template the &lt;code&gt;CanaryId&lt;/code&gt; into the bpmn before deploying it (code &lt;a href="https://github.com/jwulf/zeebe-cloud-canary/blob/master/src/ZeebeCanary.ts#L46"&gt;here&lt;/a&gt;, bpmn example &lt;a href="https://github.com/jwulf/zeebe-cloud-canary/blob/master/bpmn/canary.bpmn#L14"&gt;here&lt;/a&gt;), allowing you to namespace the canary by application, worker, application instance, worker instance, or any other resolution that makes sense.&lt;/p&gt;

&lt;p&gt;At the moment, I'm using it per-application instance. My applications have multiple workers in them, so my hypothesis here is that as long as the application instance canary has connectivity, all the workers in the application have a connection.&lt;/p&gt;

&lt;p&gt;To prevent race conditions, (for example, you name-space by application and spin up multiple instances at different times), when a worker services the chirp, it publishes a message to cancel any other instances of its name-spaced canary workflow, before starting another one.&lt;/p&gt;

&lt;p&gt;Here is the bpm diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nADMrQgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2hsobz6ovkuu7b5724a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nADMrQgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2hsobz6ovkuu7b5724a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is one idea for operational monitoring, using message correlation, and templating of a generic workflow. There are many ways that you can do it, and exactly what you monitor and how you do it depends on your tech stack and your potential failure modes.&lt;/p&gt;

&lt;p&gt;Note that you can't open the workflow in the Zeebe modeller - the process id with the template string in it doesn't validate. To create it, I edited the bpmn file in Visual Code after creating it in the modeller.&lt;/p&gt;

</description>
      <category>zeebe</category>
      <category>microservices</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Zeebe Message Correlation</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Thu, 15 Aug 2019 17:42:57 +0000</pubDate>
      <link>https://forem.com/jwulf/zeebe-message-correlation-15p4</link>
      <guid>https://forem.com/jwulf/zeebe-message-correlation-15p4</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://zeebe.io" rel="noopener noreferrer"&gt;Zeebe&lt;/a&gt; is a new workflow engine for microservices orchestration. It has first-class support for clients written in JavaScript via &lt;a href="https://www.npmjs.com/package/zeebe-node" rel="noopener noreferrer"&gt;zeebe-node&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://zeebe.io/blog/2019/08/zeebe-message-correlation/" rel="noopener noreferrer"&gt;Zeebe blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Message correlation is a powerful feature in Zeebe. It allows you to target a running workflow with a state update from an external system asynchronously. &lt;/p&gt;

&lt;p&gt;This tutorial uses the JavaScript client, but it serves to illustrate message correlation concepts that are applicable to all language clients.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://github.com/zeebe-io/zeebe-simple-monitor" rel="noopener noreferrer"&gt;Simple Monitor&lt;/a&gt; to inspect the running workflow state. Simple Monitor is a community-supported tool, and is not designed to be used in production - however, it useful during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;Here is the basic example from &lt;a href="https://docs.zeebe.io/reference/message-correlation.html" rel="noopener noreferrer"&gt;the Zeebe documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnwgc9d8o0znwirji11m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnwgc9d8o0znwirji11m6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://github.com/zeebe-io/zeebe-modeler" rel="noopener noreferrer"&gt;Zeebe Modeler&lt;/a&gt; to open the &lt;a href="https://github.com/jwulf/zeebe-message-correlation/blob/master/bpmn/test-messaging.bpmn" rel="noopener noreferrer"&gt;test-messaging&lt;/a&gt; file in &lt;a href="https://github.com/jwulf/zeebe-message-correlation" rel="noopener noreferrer"&gt;this project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click on the intermediate message catch event to see how it is configured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpv0toca6dzk8ioh6a2z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpv0toca6dzk8ioh6a2z8.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A crucial piece here is the &lt;em&gt;Subscription Correlation Key&lt;/em&gt;. In a running instance of this workflow, an incoming "&lt;em&gt;Money Collected&lt;/em&gt;" message will have a &lt;code&gt;correlationKey&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;zbc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;correlationKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Money Collected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;paymentStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The concrete value of the message &lt;code&gt;correlationKey&lt;/code&gt; is matched against running workflow instances, by comparing the supplied value against the &lt;code&gt;orderId&lt;/code&gt; variable of running instances subscribed to this message. This is the relationship established by setting the correlationKey to &lt;code&gt;orderId&lt;/code&gt; in the message catch event in the BPMN.&lt;/p&gt;

&lt;p&gt;## Running the demonstration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clone &lt;a href="https://github.com/jwulf/zeebe-message-correlation" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; npm i &amp;amp;&amp;amp; npm i -g ts-node typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In another terminal start the Zeebe Broker using the &lt;code&gt;simple-monitor&lt;/code&gt; profile from the &lt;a href="https://github.com/zeebe-io/zeebe-docker-compose" rel="noopener noreferrer"&gt;zeebe-docker-compose&lt;/a&gt; repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy the workflow and start an instance:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ts-node start-workflow.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a workflow instance with the &lt;code&gt;orderId&lt;/code&gt; set to 345:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zbc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWorkflowInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-messaging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;110110&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;paymentStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unpaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Now open Simple Monitor at &lt;a href="http://localhost:8082" rel="noopener noreferrer"&gt;http://localhost:8082&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the workflow instance. You will see the current state of the workflow:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa6fmqndx5v5tn98wmx4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa6fmqndx5v5tn98wmx4y.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The numbers above the BPMN symbols indicate that no tokens are waiting at the start event, and one has passed through; and one token is waiting at the "Collect Money" task, and none have passed through.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take a look at the "Variables" tab at the bottom of the screen. (If you don't see it you are probably looking at the workflow, rather than the instance. In that case, drill down into the instance):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxlgzjtfcsnkbing06z2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxlgzjtfcsnkbing06z2c.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that this workflow instance has the variable &lt;code&gt;orderId&lt;/code&gt; set to the value 345.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now start the workers:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ts-node workers.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Refresh Simple Monitor to see the current state of the workflow:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdcdzu9rx86au7lke2cpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdcdzu9rx86au7lke2cpu.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the token is at the message catch event, waiting for a message to be correlated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take a look at the "Message Subscriptions" tab:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F69tzd27ccjr0mdl3kk9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F69tzd27ccjr0mdl3kk9j.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that the broker has opened a message subscription for this workflow instance with the concrete value of the &lt;code&gt;orderId&lt;/code&gt; 345. This was created when the token entered the message catch event.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now send the message, in another terminal:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ts-node send-message.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Refresh Simple Monitor, and you see that the message has been correlated and the workflow has run to completion:
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/img%2Fcompleted.png"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "Message Subscriptions" tab now reports that the message was correlated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmwuc8r53dmcqreodc9ei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmwuc8r53dmcqreodc9ei.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Message Buffering
&lt;/h2&gt;

&lt;p&gt;Messages are buffered on the broker, so your external systems can emit messages before your process arrives at the catch event. The amount of time that a message is buffered is configured when publishing the message from the client library.&lt;/p&gt;

&lt;p&gt;For example, to send a message that is buffered for 10 minutes with the JavaScript client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;zbc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;correlationKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Money Collected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;paymentStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;timeToLive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600000&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how you can see it in action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the workers running.&lt;/li&gt;
&lt;li&gt;Publish the message:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Click on "Messages" at the top of the Simple Monitor page. You will see the message buffered on the broker:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwolznr2jcwnm5q5qhtyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwolznr2jcwnm5q5qhtyt.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now start another instance of the workflow:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see that the message is correlated to the workflow instance, even though it arrived before the workflow instance was started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;A couple of common gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;correlationKey&lt;/code&gt; in the BPMN message definition is the name of the workflow variable to match against. The &lt;code&gt;correlationKey&lt;/code&gt; in the message is the concrete value to match against that variable in the workflow instance. Arguably, it might be more appropriately named &lt;code&gt;correlationValue&lt;/code&gt; to make this distinction clearer. There is &lt;a href="https://github.com/zeebe-io/zeebe/issues/2718" rel="noopener noreferrer"&gt;a GitHub issue&lt;/a&gt; around that - feel free to add your feedback.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An important thing to know is that the message subscription &lt;em&gt;is not updated after it is opened&lt;/em&gt;. That is not an issue in the case of a message catch event, however for boundary message events (both interrupting and non-interrupting) the subscription is opened &lt;em&gt;as soon as the token enters the bounding subprocess&lt;/em&gt;. If any service task modifies the &lt;code&gt;orderId&lt;/code&gt; value inside the subprocess, the subscription will not be updated.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For example, the interrupting boundary message event in the following example will not be correlated on the updated value, because the subscription is opened when the token enters the subprocess, using the value at that time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6yzgdv5r1bwigq93138o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6yzgdv5r1bwigq93138o.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you need a boundary message event correlated on a value that is modified somewhere in your process, then put the boundary message event in a subprocess after the task that sets the variable. The message subscription for the boundary message event will be opened when the token enters the subprocess, with the current variable value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fge32800ujlciubxlbubq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fge32800ujlciubxlbubq.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Message Correlation is a powerful feature in Zeebe. Knowing how messages are correlated, and how and when the message subscription is created is important to design systems that perform as expected.&lt;/p&gt;

&lt;p&gt;And Simple Monitor is a useful tool for inspecting the behavior of a Zeebe system to figure out what is happening during development.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to select an Open Source Project to contribute to</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Fri, 10 May 2019 12:48:07 +0000</pubDate>
      <link>https://forem.com/jwulf/how-to-select-an-open-source-project-to-contribute-to-11mh</link>
      <guid>https://forem.com/jwulf/how-to-select-an-open-source-project-to-contribute-to-11mh</guid>
      <description>&lt;p&gt;I'm going to ask two questions. Just two questions.&lt;/p&gt;

&lt;p&gt;It's a koan. &lt;/p&gt;

&lt;p&gt;Meditating on these questions will reveal everything to you.&lt;/p&gt;

&lt;p&gt;The answer to these questions is not the answer to which project to contribute to, but meditating on these questions will reveal the project to you when you encounter it.&lt;/p&gt;

&lt;p&gt;Here are the questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. How many commits does it take to become the #2 committer in the world on Immutable.js?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. If you did that, where would you end up working?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;About me: &lt;em&gt;I spent three years as a recruiter analyzing GitHub to find developers. Also, I'm a professional software developer, and I've worked in open source for over a decade. &lt;a href="https://dev.to/jwulf/how-to-get-a-job-through-doing-open-source-1a17"&gt;Read about how I got my last job by committing to open source&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>career</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>How to Get a Job Through Doing Open Source</title>
      <dc:creator>Josh Wulf</dc:creator>
      <pubDate>Fri, 10 May 2019 12:09:41 +0000</pubDate>
      <link>https://forem.com/jwulf/how-to-get-a-job-through-doing-open-source-1a17</link>
      <guid>https://forem.com/jwulf/how-to-get-a-job-through-doing-open-source-1a17</guid>
      <description>&lt;p&gt;&lt;em&gt;In 2017, as a recruiter, I wrote an article about getting hired from GitHub contributions. Hacker News &lt;a href="https://news.ycombinator.com/item?id=13705055" rel="noopener noreferrer"&gt;hated on it so hard&lt;/a&gt; that I quit my job as a recruiter, got a job as a developer, spent 14 months contributing on GitHub, and got myself hired from it to work on &lt;a href="https://zeebe.io" rel="noopener noreferrer"&gt;Zeebe.io&lt;/a&gt;. Turns out it does work!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My 16-year-old son, Prahlad, just walked into our apartment. &lt;/p&gt;

&lt;p&gt;"&lt;em&gt;What did he say???&lt;/em&gt;" I ask.&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;He said 'Yes'.&lt;/em&gt;" &lt;/p&gt;

&lt;p&gt;Understated, playing it cool, like many teenagers do with their parents. But I know he's deeply excited, and probably a little bit scared.&lt;/p&gt;

&lt;p&gt;He just got a gig working at the table-top and role-playing store in the building next to our apartment block in Brisbane, Australia.&lt;/p&gt;

&lt;p&gt;I coached him how to get it. I saw that he was enthusiastic about the games they sell there. He plays &lt;em&gt;Magic - The Gathering&lt;/em&gt; (MTG) there with his friends, and a couple of weeks ago I came home to twenty teenagers (yes - 20!) in our apartment playing the game. He went to the recent MTG pre-release and played it all day.&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;Why don't you ask Sean if you can do some part-time work in the store?&lt;/em&gt;" I suggested. Sean is the owner of the store.&lt;/p&gt;

&lt;p&gt;Prahlad asked Sean, and Sean said that he doesn't have any work that needs to be done there. &lt;/p&gt;

&lt;p&gt;"&lt;em&gt;Go back and tell him that you want to get work experience,&lt;/em&gt;" I told him. "&lt;em&gt;Tell him you'll do it for free. That way you can learn from him. He is running a successful business, and you can learn that. You can learn how to DM, how to run events, and how to do customer service. You might find ways to expand the business. And if it doesn't turn into a paying gig, when the time comes you can get a job at another store based on your experience.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;So he had that conversation, and he starts tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Impact GitHub is Having on Your Software Career, Right Now...
&lt;/h2&gt;

&lt;p&gt;In 2017, I wrote my (so-far) most popular article of all time: &lt;a href="https://medium.com/@sitapati/the-impact-github-is-having-on-your-software-career-right-now-6ce536ec0b50" rel="noopener noreferrer"&gt;The Impact GitHub is Having on Your Software Career, Right Now…&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In that article I cast the vision for how you can develop your career through open-source contribution.&lt;/p&gt;

&lt;p&gt;It clearly struck a nerve - it got &lt;a href="https://news.ycombinator.com/item?id=13705055" rel="noopener noreferrer"&gt;382 points and 237 comments on Hacker News&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many of the comments disagreed with my main premise, but I felt that they had missed the point.&lt;/p&gt;

&lt;p&gt;They said that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My experience at Red Hat was atypical. There are no other opportunities like that.&lt;/li&gt;
&lt;li&gt;I didn't know what I was talking about because I was a recruiter (they must have skipped my ten years in engineering at Red Hat part)&lt;/li&gt;
&lt;li&gt;It was unrealistic advice for developers with a day job in companies working in BitBucket.&lt;/li&gt;
&lt;li&gt;It wasn't possible for devs who work in companies in the financial sector with high regulation and security.&lt;/li&gt;
&lt;li&gt;It wasn't possible if you weren't willing to sacrifice your life outside work.&lt;/li&gt;
&lt;li&gt;It just wasn't possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is nothing that I love more than a challenge, so I went "deep cover". &lt;/p&gt;

&lt;p&gt;I quit my job as a recruiter, and got a job as a software engineer in a pure closed-source company that uses BitBucket and has PCI-compliant security.&lt;/p&gt;

&lt;p&gt;Fourteen months later, I got hired by Camunda to work as the Developer Advocate for &lt;a href="https://zeebe.io" rel="noopener noreferrer"&gt;Zeebe&lt;/a&gt; - a workflow engine for orchestrating microservices, purely based on my open-source contributions while working at that job. I just did everything that I advised readers to do in the comments of &lt;a href="https://medium.com/@sitapati/the-impact-github-is-having-on-your-software-career-right-now-6ce536ec0b50" rel="noopener noreferrer"&gt;my original Medium article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While I was doing that, I didn't sacrifice my hobbies or my family, in fact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I kept building my side-hustle &lt;a href="https://medium.freecodecamp.org/how-to-mod-minecraft-without-java-f076ddaec01c" rel="noopener noreferrer"&gt;Magikcraft.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I did a launch event for &lt;a href="https://www.mct1.io" rel="noopener noreferrer"&gt;Minecraft for Type 1 Diabetes&lt;/a&gt; that was filmed for Mojang's YouTube channel.&lt;/li&gt;
&lt;li&gt;I trained for and competed in three physique competitions with my wife - it was on her bucket list to win a bikini-modelling competition. She won her division, and I eventually placed 2nd in a State-level competition.&lt;/li&gt;
&lt;li&gt;I trained to lead a personal development course.&lt;/li&gt;
&lt;li&gt;I did a three-month long course with my son.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't say this to brag - just to show that it's not a zero-sum game, as some people paint it: "&lt;em&gt;Oh that's easy to say, but it privileges people who are willing to work for free and sacrifice their time with their family.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course, for the person who starts with the conclusion that "&lt;em&gt;it's not possible&lt;/em&gt;", no amount of evidence is ever going to be enough - and to those I say: "&lt;em&gt;What do you want to see now?&lt;/em&gt;" &lt;/p&gt;

&lt;p&gt;Not because it will change their mind, but because it's a fertile source of inspiration for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconceptions and Strawman Arguments
&lt;/h2&gt;

&lt;p&gt;Before I sketch out how to do it, I want to explicitly state a couple of objections that just &lt;em&gt;miss the point&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can't tell anything about a developer from their contribution graph on GitHub.&lt;/li&gt;
&lt;li&gt;I've hired lots of developers and I've never looked at their GitHub profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me just say this: &lt;em&gt;you aren't going to get hired through your open-source contributions by these people&lt;/em&gt;. That's all those things say. There is more I could say about that, but I'm not going to address them any further.&lt;/p&gt;

&lt;p&gt;I also want to make it clear: I'm not saying that you have to do this. Just that you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;As I pointed out to commenters on &lt;a href="https://medium.com/@sitapati/the-impact-github-is-having-on-your-software-career-right-now-6ce536ec0b50" rel="noopener noreferrer"&gt;the original Medium article&lt;/a&gt;, in your day job you either use open-source libraries, or may be able to introduce some to your stack on new projects.&lt;/p&gt;

&lt;p&gt;If you don't have a day job, then you can introduce them to your stack easily.&lt;/p&gt;

&lt;p&gt;While doing research for a new project at my day job, I tried a number of different projects - including a TypeScript gRPC server. I found a bug in it, and opened &lt;a href="https://github.com/xuezier/grpc-server-ts/issues/7" rel="noopener noreferrer"&gt;an issue&lt;/a&gt;. Then I wrote a patch for it, that got &lt;a href="https://github.com/xuezier/grpc-server-ts/pull/8" rel="noopener noreferrer"&gt;merged in&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We didn't use that project, but it was just part of being a member of a global community - like picking up a piece of paper in the hallway of our apartment building or on the street.&lt;/p&gt;

&lt;p&gt;We eventually did use a gRPC library, and we needed it to support gRPC streams. So I wrote a patch for that, and &lt;a href="https://github.com/zetogk/node-grpc-client/pull/7" rel="noopener noreferrer"&gt;it got merged&lt;/a&gt;. That contribution was enough to get me a mention in &lt;a href="https://www.npmjs.com/package/node-grpc-client" rel="noopener noreferrer"&gt;the contributors on npm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqppfqnr60l47545l6xeq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqppfqnr60l47545l6xeq.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I opened issues and submitted patches to Netflix Conductor (&lt;a href="https://github.com/Netflix/conductor/pull/888" rel="noopener noreferrer"&gt;example&lt;/a&gt;). Nothing earth-shattering, just contributing to the environment I live in.&lt;/p&gt;

&lt;p&gt;One of the things I explained to management was that a key decision-making factor in which technologies to adopt is how fast issues get addressed, and if we patch something for our own production use-case, how amenable the maintainers are to accepting our pull requests to the mainstream.&lt;/p&gt;

&lt;p&gt;No-one involved in hiring me at Camunda looked at these (&lt;em&gt;callback to the peeps who say they never look at people's contributions on GitHub - X-Factor judges don't watch you practicing in front of the mirror either - just sayin'&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;I will say, however, that smart recruiters at Google and Facebook would hit me up based on watching my activity. It's not enough to land a job, but it does have people come looking for you - especially as you build up a history of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Break
&lt;/h2&gt;

&lt;p&gt;The big break came when we landed on using &lt;a href="https://zeebe.io" rel="noopener noreferrer"&gt;Zeebe&lt;/a&gt; as the orchestration engine for our new microservices project.&lt;/p&gt;

&lt;p&gt;I was over the moon, because Zeebe had an officially-supported Go client, and this was my chance to get our team coding in Go. I'd done some POCs and side-projects in it, but we coded in JavaScript.&lt;/p&gt;

&lt;p&gt;No-one else on the team of six was keen to make the move, however, so we needed a JavaScript client.&lt;/p&gt;

&lt;p&gt;I managed to get alignment on using TypeScript for the new project, and so I created a TypeScript client library.&lt;/p&gt;

&lt;p&gt;I had logged some issues and contributed some small patches to Zeebe, as part of the civic duty / evaluation. Now, I pitched to management making the client library open-source.&lt;/p&gt;

&lt;p&gt;My argument for it had two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It means that our library has the opportunity to become the most widely-used one, which means it gets more eyeballs on it, and the possibility of patches from the wider community. Meaning that we don't end up maintaining something internally and finding out later on that there is a more widely-used/supported one that we have to rebase on.&lt;/li&gt;
&lt;li&gt;It is a good developer-marketing tool to raise our profile, build our engineering brand, and differentiate us in the market when hiring.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I got alignment. However, the company had no GitHub presence. I got that by explaining that we needed a GitHub organisation in order to collaborate with the engineers at Camunda.&lt;/p&gt;

&lt;p&gt;This is a company with no prior experience in Open Source. I had the advantage that I spent ten years working in it, so I knew both that it works, and how it works. Looking back at it, I probably wouldn't have stood for it the way I did if I had an ounce of doubt - if I "listened to the haters" in the comments.&lt;/p&gt;

&lt;p&gt;Once that was in place, we pushed &lt;a href="https://github.com/CreditSenseAU/zeebe-client-node-js" rel="noopener noreferrer"&gt;the library&lt;/a&gt; live, and published it to &lt;a href="https://www.npmjs.com/package/zeebe-node" rel="noopener noreferrer"&gt;NPM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the things that can you develop over time participating in open-source as a civic member, is a sixth sense of opportunities. I could see that this was an exciting technology that was poised to land on a rising wave - and we were about to ride that wave too. And there was no JS client.&lt;/p&gt;

&lt;p&gt;It took over a year for the factors to align, but if I were sitting at my desk committing to BitBucket (yes, I was committing code to private BitBucket repos too) &lt;em&gt;only&lt;/em&gt;, then I wouldn't see an opportunity like that in ten years.&lt;/p&gt;

&lt;p&gt;I wrote an &lt;a href="//https//medium.com/@sitapati/node-js-client-for-zeebe-microservices-orchestration-engine-72287e4c7d94"&gt;article on Medium.com announcing the library&lt;/a&gt;. I actually got in trouble for that. I jumped the gun and didn't get alignment for it before publishing. Lesson learned.&lt;/p&gt;

&lt;p&gt;However, the library, the article, and my participation in issues, patches, and the Slack channel got me noticed by the folks at Camunda.&lt;/p&gt;

&lt;p&gt;Enough so, that when I was looking for another role, they acted fast to hire their first employee in Australia - faster than local companies moved, even with their time zone and established infrastructure advantage.&lt;/p&gt;

&lt;p&gt;On a call with Berd Rücker - one of Camunda's cofounders - I shared why I thought it was a good match: at Red Hat we would often hire or acqui-hire from the community (&lt;em&gt;*acqui-hire means you bring an entire project in, people and tech - it's a portmanteau of "acquire" and "hire"&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;We would "&lt;em&gt;find someone who was already doing it, and just pay them to do it for us&lt;/em&gt;". Normally when you hire, you find someone, pay them, and hope that they will enjoy what they are doing and be good at it. Hiring open-source contributors reduces the risk. They still might turn out to be terrible to work with, but you have a sense already of how you work together, and you know what their work is like.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a universal principle
&lt;/h2&gt;

&lt;p&gt;So it's the same thing for my son. He has the &lt;em&gt;opportunity&lt;/em&gt; to demonstrate value, build trust, and gain skills at the gaming store. And when the time is right, he will start getting paid - either there, or elsewhere.&lt;/p&gt;

&lt;p&gt;At the careers fair for Red Hat in 2004, I brought along a 20-page paper I had written where I predicted the future of tech: open-source eating the world; devices shrinking to hand-helds; emergent network effects. I wrote it on breaks while working on an ISP help desk, between installing Linux on old computers I bought for $20 a pop.&lt;/p&gt;

&lt;p&gt;In a recent Slack discussion, someone said: "&lt;em&gt;The problem with Open Source contribution is that it privileges those who have time to do it&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Firstly, that's the &lt;em&gt;opportunity&lt;/em&gt; of Open Source, not the problem. Secondly, everyone has the time - and I would say civic duty if you use it - to contribute &lt;em&gt;something&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And lastly: yeah, that's right. Just like people who have the time to play clubs and pubs every Friday night, and practice their chops in their bedroom are privileged to be able to be discovered by an A&amp;amp;R talent scout and get a record deal.&lt;/p&gt;

&lt;p&gt;The Beatles &lt;a href="https://en.wikipedia.org/wiki/The_Beatles_in_Hamburg" rel="noopener noreferrer"&gt;played for two years straight in Hamburg&lt;/a&gt; to hone their skills. As a professional software developer, if you are going to hone your craft anyway, you might as well do it in a way that contributes to a greater civic good and your own portable, public reputation.&lt;/p&gt;

&lt;p&gt;Or not. Whatever. &lt;/p&gt;

&lt;p&gt;I'm just saying that it &lt;em&gt;is&lt;/em&gt; possible.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>career</category>
    </item>
  </channel>
</rss>
