<?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>DEV Community: Health Place</title>
    <description>The latest articles on DEV Community by Health Place (@healthplace).</description>
    <link>https://dev.to/healthplace</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%2Forganization%2Fprofile_image%2F4434%2F778874b5-72a6-467d-bc73-a8041b87e7bd.png</url>
      <title>DEV Community: Health Place</title>
      <link>https://dev.to/healthplace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/healthplace"/>
    <language>en</language>
    <item>
      <title>Contextual filtering with ChatGPT</title>
      <dc:creator>Matt Inamdar</dc:creator>
      <pubDate>Thu, 30 Mar 2023 21:57:13 +0000</pubDate>
      <link>https://dev.to/healthplace/contextual-filtering-with-chatgpt-1jci</link>
      <guid>https://dev.to/healthplace/contextual-filtering-with-chatgpt-1jci</guid>
      <description>&lt;p&gt;When data is ingested into &lt;a href="https://healthplace.io" rel="noopener noreferrer"&gt;Health Place&lt;/a&gt;, it goes through a series of computation and transformation steps, forming an "ingestion pipeline".&lt;/p&gt;

&lt;p&gt;One of the essential steps in the pipeline is tagging the listing with concepts from our purpose-built knowledge graph.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbto8nauqhfroj5hs6lls.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbto8nauqhfroj5hs6lls.png" alt="Graph representation of vocabulary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can think of concepts as taxonomies, a vocabulary to give semantic meaning to your data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For quite a while now, we've already implemented AI, specifically &lt;a href="https://en.wikipedia.org/wiki/Natural_language_processing" rel="noopener noreferrer"&gt;Natural Language Processing&lt;/a&gt; (NLP), into our pipeline by using a &lt;a href="https://en.wikipedia.org/wiki/Bag-of-words_model" rel="noopener noreferrer"&gt;Bag-of-Words model&lt;/a&gt; (BoW) to extract concepts from the text provided. And for the most part, this has worked exceptionally well!&lt;/p&gt;

&lt;p&gt;It falls short, though. &lt;/p&gt;




&lt;p&gt;When the context surrounding the extracted concept gives it a different meaning, the BoW model doesn't care. Only if the concept is present in the text – in some cases, this can become an issue.&lt;/p&gt;

&lt;p&gt;Take the following example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Supporting You is a seven-week programme which equips young people with the tools to help themselves to strengthen their resilience and emotional well-being.&lt;/p&gt;

&lt;p&gt;The programme is suitable for young people who meet the following criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aged 11-17&lt;/li&gt;
&lt;li&gt;Beginning to exhibit behaviours, or suggest themselves that they are starting to be affected by low mood, stress or anxiety such that it is beginning to interfere with the enjoyment of life and normal activities&lt;/li&gt;
&lt;li&gt;Have no current intervention or support in place from any other agency for emotional wellbeing or mental health issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;They do not meet the criteria for a CAMHS referral&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Are able to commit to attending a 7 week Supporting You programme in their local area&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's focus on the boldened piece of text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;They do not meet the criteria for a CAMHS referral&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;CAHMS is an acronym for Child and Adolescent Mental Health Services.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this example, you can see that CAHMS is mentioned in an exclusionary context. This text &lt;em&gt;mentions&lt;/em&gt; CAHMS but &lt;em&gt;isn't relevant to&lt;/em&gt; CAHMS.&lt;/p&gt;

&lt;p&gt;Our previous process couldn't consider this context and would therefore tag this listing with "CAHMS", resulting in a precision problem.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fille5ar5r4z859ksfgeq.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fille5ar5r4z859ksfgeq.png" alt="Diagram of precision vs recall"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Precision&lt;/strong&gt; and &lt;strong&gt;recall&lt;/strong&gt; are like buckets in a well; by raising one, you lower the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Precision&lt;/strong&gt; measures what % of results are accurate, whereas &lt;strong&gt;recall&lt;/strong&gt; measures what % of relevant results were actually returned.&lt;/p&gt;

&lt;p&gt;So we could tweak our NLP step by improving it's precision, but at the expense of lowering its recall. But only if there was another way.&lt;/p&gt;

&lt;p&gt;Enter ChatGPT...&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4docr92fcpune3a9arc.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4docr92fcpune3a9arc.jpg" alt="ChatGPT logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT is perfect for understanding context and the use case was immediately obvious to me.&lt;/p&gt;

&lt;p&gt;The technique I had in mind was to pass GPT the source text along with the concepts from our knowledge graph that we've extracted. We can then ask GPT to tell us which extracted taxonomies are relevant to the text and which aren't.&lt;/p&gt;

&lt;p&gt;This allows us to engineer our previous NLP layer towards higher recall to extract as many relevant concepts as possible and, in the next layer, have GPT filter out the concepts that aren't relevant, providing us with the precision needed.&lt;/p&gt;

&lt;p&gt;This process seemingly allows us to have our cake and eat it! Something seldom one gets to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get in touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions, comment below, or find me on LinkedIn: &lt;a href="https://www.linkedin.com/in/matthew-inamdar/" rel="noopener noreferrer"&gt;linkedin.com/in/matthew-inamdar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you'd like to work for a tech-for-good company like &lt;a href="https://healthplace.io" rel="noopener noreferrer"&gt;Health Place&lt;/a&gt;, then message me for an informal chat at &lt;a href="//matt@healthplace.io"&gt;matt@healthplace.io&lt;/a&gt; – we're currently looking for a Frontend Engineer!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image sources:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://www.v7labs.com/blog/precision-vs-recall-guide" rel="noopener noreferrer"&gt;https://www.v7labs.com/blog/precision-vs-recall-guide&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://www.klippa.com/en/blog/information/what-is-chatgpt/" rel="noopener noreferrer"&gt;https://www.klippa.com/en/blog/information/what-is-chatgpt/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chatgpt</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Dynamically optimising and caching images via a Node.js microservice</title>
      <dc:creator>Matt Inamdar</dc:creator>
      <pubDate>Sat, 09 Apr 2022 12:43:02 +0000</pubDate>
      <link>https://dev.to/healthplace/dynamically-optimising-and-caching-images-via-a-nodejs-microservice-182j</link>
      <guid>https://dev.to/healthplace/dynamically-optimising-and-caching-images-via-a-nodejs-microservice-182j</guid>
      <description>&lt;p&gt;At &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt; we are pushing towards a media heavy platform filled with images and videos to make it more engaging for our users, enabling them to get a better idea of the support listings they may be interested in.&lt;/p&gt;

&lt;p&gt;Along with this comes the big challenge of keeping things &lt;strong&gt;fast and efficient&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article we'll go through: where we started along with the problems we were facing, the ideal solution we had in mind, and finally the solution we ended up with.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR: We built a microservice in Node.js to serve our images with dynamic optimisation provided via query string params. We then placed this behind a CDN to cache indefinitely.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Each listing on &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt; has support for a variety of images, not limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listing logo&lt;/li&gt;
&lt;li&gt;Providing organisation logo&lt;/li&gt;
&lt;li&gt;Listing gallery (there can be many images in here!)&lt;/li&gt;
&lt;li&gt;Location image (a listing can have many locations)&lt;/li&gt;
&lt;li&gt;Soon listings will also have a wide banner image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start by discussing how these images are provided to us, and where they're stored.&lt;/p&gt;

&lt;p&gt;All images are uploaded via our admin web app which is where organisations can login and manage their listings on the site.&lt;/p&gt;

&lt;p&gt;This all goes through our primary PHP-based API which in turn, uploads the image to Amazon S3, our cloud storage service of choice.&lt;/p&gt;

&lt;p&gt;Initially we had endpoints provided by that same API to serve the images. The API would need to download the image from S3 each time and return it. This quickly became an issue as PHP is a blocking language, meaning no other requests could be handled whilst the image was being downloaded and returned.&lt;/p&gt;

&lt;p&gt;Another issue was present. The uploaded images were almost always not optimised. They were often large in resolution and file size, making them not friendly at all for consumption via the frontend web app.&lt;/p&gt;

&lt;p&gt;To counter this, we implemented image optimisation &lt;strong&gt;at time of upload&lt;/strong&gt; by pushing a job to optimise the image once uploaded. This worked, but we started introducing ever growing complexity and the time came to consider moving this logic out of PHP entirely...&lt;/p&gt;

&lt;h2&gt;
  
  
  The ideal solution
&lt;/h2&gt;

&lt;p&gt;The ideal plan was to have a completely separate microservice that is responsible for the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading images (behind auth)&lt;/li&gt;
&lt;li&gt;Streaming images&lt;/li&gt;
&lt;li&gt;API to confirm image exists&lt;/li&gt;
&lt;li&gt;Dynamically optimising images via query string parameters&lt;/li&gt;
&lt;li&gt;CDN to cache images and optimised versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PHP API would then return a field in the JSON response, telling the client to go to the Images Microservice to get the image, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Listing name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logo_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://images.healthplace.io/image-id"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client could then append some query string parameters to dynamically request a version optimised for its specific use case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://images.healthplace.io/image-id?format=jpg&amp;amp;quality=80&amp;amp;height=250"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, this should be placed behind a CDN resulting in only the first request having to wait for the microservice to download the image from S3 and optimise it. All subsequent requests for that image with those exact optimisations would be returned immediately from the CDN.&lt;/p&gt;

&lt;p&gt;This greatly simplifies our workflow as now an image would only need to be uploaded once in its raw and unoptimised state. All optimisations are then achieved dynamically at time of use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implementation
&lt;/h2&gt;

&lt;p&gt;First, a quick note:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In our first iteration, we've managed to push the streaming, optimisation, and caching logic to a newly created Images Microservice. However, the uploading of new images and persistence to S3 is still achieved through our main API. Our next iteration will push this logic into the Images Microservice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So what did we do?&lt;/p&gt;

&lt;p&gt;First, we created a standard express app using TypeScript (nothing special here). Then we pulled in this extremely useful package called &lt;a href="https://www.npmjs.com/package/express-sharp"&gt;express-sharp&lt;/a&gt; that wraps &lt;a href="https://www.npmjs.com/package/sharp"&gt;sharp&lt;/a&gt;, a Node.js image manipulation library, in an express middleware.&lt;/p&gt;

&lt;p&gt;We then setup the middleware to listen to any route invocation that began with &lt;code&gt;/_/&lt;/code&gt; which would use the S3 adapter to pull the image from S3. This would allow for &lt;code&gt;/_/my-image-id&lt;/code&gt; to be passed to the adapter with a key of &lt;code&gt;my-image-id&lt;/code&gt;, correlating to the file path in the S3 bucket.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;There's also a TypeScript interface provided to write your own adapters which we utilised.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Query string parameter based optimisations are provided out of the box, so no need to do anything fancy here!&lt;/p&gt;

&lt;p&gt;We then provided two domains for the microservice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Origin &lt;code&gt;https://images-api.healthplace.io&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CDN &lt;code&gt;https://cdn.images-api.healthplace.io&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CDN is setup to make downstream requests to the origin domain on cache misses, and the CDN domain is also used in our API responses. As part of the CDN config, we set the query string optimisation parameters as part of the cache key to ensure we hit the cache as much as possible.&lt;/p&gt;

&lt;p&gt;We now have a fully working Images Microservice!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We also added further endpoints and adapters to account for other useful situations, such as returning SVG's and files from the filesystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Building upon this, we'd like to provide support for directly uploading images to this microservice, allowing our main API to simply accept the corresponding ID's only. The Images Microservice could then provide an endpoint for the main API to validate the image ID as existing. There's also scope to dynamically add watermarks and all sorts of other manipulations!&lt;/p&gt;

&lt;p&gt;But that's all I have for now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Get in touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions, comment below, and I'll get back to you.&lt;/p&gt;

&lt;p&gt;And if you think you'd like to work somewhere like &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;, then message me for a chat at &lt;a href="//mailto:matt@healthplace.io"&gt;matt@healthplace.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@warrenumoh?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Warren Umoh&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/render?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>microservices</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Consuming RabbitMQ Queues in Laravel</title>
      <dc:creator>Matt Inamdar</dc:creator>
      <pubDate>Wed, 26 Jan 2022 14:14:18 +0000</pubDate>
      <link>https://dev.to/healthplace/consuming-rabbitmq-queues-in-laravel-4ok9</link>
      <guid>https://dev.to/healthplace/consuming-rabbitmq-queues-in-laravel-4ok9</guid>
      <description>&lt;p&gt;RabbitMQ is an open-source message broker with support for queues, exchanges and topics.&lt;/p&gt;

&lt;p&gt;It's the "most widely deployed" open-source message broker at that (according to &lt;a href="https://www.rabbitmq.com/"&gt;their website&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We decided to use RabbitMQ at &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt; in order to enable asynchronous event-based systems to complete tasks, only when the previous task had completed.&lt;/p&gt;

&lt;p&gt;Our exact scenario was to get our ingestion pipeline working, which is composed of multiple services, in different languages. &lt;/p&gt;

&lt;p&gt;RabbitMQ allows us to have a single service in the ingestion pipeline complete its task, push a message to RabbitMQ, and then have the next downstream service start its task (which may then do something similar, until the data is finally ingested).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use Laravel's queue system?
&lt;/h2&gt;

&lt;p&gt;This is a good question, and ideally we'd have preferred it if this was possible.&lt;/p&gt;

&lt;p&gt;The problem is to do with how messages are stored in the Laravel queue system.&lt;/p&gt;

&lt;p&gt;Laravel calls messages "jobs", which are represented as Plain Old PHP Objects (POPO's). These jobs are then serialised into a string and pushed onto the queue driver configured.&lt;/p&gt;

&lt;p&gt;This implementation is fine when the publisher and consumer are the same app. This is usually the case when your Laravel app needs to asynchronously perform a task (such as sending an email).&lt;/p&gt;

&lt;p&gt;The problem appears when you introduce several apps, as is the case with our ingestion pipeline.&lt;/p&gt;

&lt;p&gt;Our primary API, sitting in front of our database, is our Laravel app. Further upstream in our ingestion pipeline, we have a series of Node.js microservices responsible for several things, such as normalising and validating the data.&lt;/p&gt;

&lt;p&gt;These microservices and Laravel need a shared way to communicate with one another via asynchronous events. The way to achieve that is by using a message broker (RabbitMQ) and publishing/consuming messages in JSON.&lt;/p&gt;

&lt;p&gt;This works great as JSON is widely used as a standard interface format and has support in almost all modern languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not used webhooks instead of a queue?
&lt;/h2&gt;

&lt;p&gt;Another good question, and the answer is to do with "simplicity".&lt;/p&gt;

&lt;p&gt;If we were to go down the webhook approach, it would require the consumer of messages to now host a HTTP endpoint, which must always be up, in order to receive messages. This is troublesome for services that aren't web-facing.&lt;/p&gt;

&lt;p&gt;This also means that the publisher needs to invoke this HTTP endpoint as well as introducing retry logic if the endpoint is ever down. &lt;em&gt;And what should happen is all retries have failed?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A queue on the other hand, allows the message to be published and then any listener can consume this message if they're interested. An added benefit is that multiple services can consume the same message, even though the publisher only published it once.&lt;/p&gt;

&lt;p&gt;This allows for great decoupling and flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Now for the fun part! Let's take a look at how we implemented this at &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the RabbitMQ package
&lt;/h3&gt;

&lt;p&gt;Start by installing this package which is a Laravel wrapper for the &lt;a href="https://packagist.org/packages/php-amqplib/php-amqplib"&gt;php-amqplib/php-amqplib&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer &lt;span class="nb"&gt;install &lt;/span&gt;bschmitt/laravel-amqp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, make sure to register it as a service provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/app.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;

    &lt;span class="s1"&gt;'providers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;//...&lt;/span&gt;

        &lt;span class="nc"&gt;Bschmitt\Amqp\AmqpServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&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;And finally to publish the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--provider&lt;/span&gt; Bschmitt&lt;span class="se"&gt;\\&lt;/span&gt;Amqp&lt;span class="se"&gt;\\&lt;/span&gt;AmqpServiceProvider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be sure to check the newly created &lt;code&gt;config/amqp.php&lt;/code&gt; file and add your credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the consumer Artisan command
&lt;/h3&gt;

&lt;p&gt;In order to start consuming messages from RabbitMQ, we need to create an infinitely running Artisan command that will continuously check for messages on the exchange.&lt;/p&gt;

&lt;p&gt;Here's a working example of such a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Console\Commands\HealthPlace\RabbitMQ&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Exceptions\InvalidAMQPMessageException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Jobs\IngestDataJob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bschmitt\Amqp\Amqp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bschmitt\Amqp\Consumer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Console\Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Bus\DispatchesJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpAmqpLib\Message\AMQPMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Log\LoggerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsumeCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DispatchesJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'healthplace:rabbitmq:consume'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Runs a AMQP consumer that defers work to the Laravel queue worker'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Amqp&lt;/span&gt; &lt;span class="nv"&gt;$consumer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;LoggerInterface&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Listening for messages...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$consumer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AMQPMessage&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Consumer&lt;/span&gt; &lt;span class="nv"&gt;$resolver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Consuming message...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&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="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validateMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Message received'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IngestDataJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'filepath'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
                    &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Message handled.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$resolver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;acknowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InvalidAMQPMessageException&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Message failed validation.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$resolver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Message is not valid JSON.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$resolver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&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;span class="s1"&gt;'routing'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ingest.pending'&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="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Consumer exited.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validateMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&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="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'filepath'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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="nc"&gt;InvalidAMQPMessageException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'The [filepath] property must be a string.'&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;There's a few things going on here so let's break this down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The payload (the message content) is JSON decoded, with an exception being thrown if the JSON is malformed.&lt;/li&gt;
&lt;li&gt;The JSON itself is then validated to ensure the that it contains the fields in the format that we expect (very similar to request validation).&lt;/li&gt;
&lt;li&gt;A standard Laravel job is then pushed for the relevant task to be performed asynchronously.&lt;/li&gt;
&lt;li&gt;The message is acknowledged to RabbitMQ so it is aware that the consumer has successfully consumed the message.&lt;/li&gt;
&lt;li&gt;If the JSON fails to decode or if validation fails, the message is rejected to RabbitMQ.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You may be wondering what the purpose is for publishing a Laravel job from this command, and although it is completely possible to work the message directly in this command, it introduces some problems...&lt;/p&gt;

&lt;p&gt;In our case, the ingestion job takes a long time, potentially hours depending on the size of the dataset. If this consume command was to process that, it would mean that we couldn't consume any further messages from RabbitMQ until the ingestion had finished.&lt;/p&gt;

&lt;p&gt;What we opted to do instead, was to push a Laravel job from this and have a queue worker actually do the ingestion. We can even scale up our queue workers if we have a bunch of jobs waiting to be worked, all whilst our RabbitMQ consumer is actively listening for further messages.&lt;/p&gt;

&lt;p&gt;And that wraps it up!&lt;/p&gt;

&lt;h2&gt;
  
  
  Get in touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions, comment below, and I'll get back to you.&lt;/p&gt;

&lt;p&gt;And if you think you'd like to work somewhere like &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;, then message me for a chat at &lt;a href="mailto:matt@healthplace.io"&gt;matt@healthplace.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>rabbitmq</category>
      <category>queue</category>
    </item>
    <item>
      <title>Continuous Delivery at Health Place</title>
      <dc:creator>Matt Inamdar</dc:creator>
      <pubDate>Wed, 14 Jul 2021 14:39:26 +0000</pubDate>
      <link>https://dev.to/healthplace/continuous-delivery-at-health-place-2ln5</link>
      <guid>https://dev.to/healthplace/continuous-delivery-at-health-place-2ln5</guid>
      <description>&lt;p&gt;We hear the terms "CI" and "CD" thrown around a lot these days, and I've worked on many projects that have implemented a CI/CD pipeline, some large with low user counts, some small with large user counts, as well as everything in-between.&lt;/p&gt;

&lt;p&gt;I've seen what works well and what doesn't.&lt;/p&gt;

&lt;p&gt;With this experience, we've implemented a CI/CD pipeline at &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt; that provides us with all the benefits of Continuous Delivery, wrapped up in a simple yet elegant implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal
&lt;/h2&gt;

&lt;p&gt;Before we get into it, let's quickly address the reason behind all of this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the purpose of the CI/CD pipeline?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As the literature states, the crux of the CI and CD pipeline is to automate as much of the process as possible in order to get committed code up to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI pipeline
&lt;/h2&gt;

&lt;p&gt;The goals of the CI pipeline are to make sure that any code committed is fit for a release candidate (code that is fit for deployment to any environment).&lt;/p&gt;

&lt;p&gt;So, what do we do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger a CI build from any commit pushed to origin.&lt;/li&gt;
&lt;li&gt;Build our dev Docker images.&lt;/li&gt;
&lt;li&gt;Run automated tests.&lt;/li&gt;
&lt;li&gt;Build our prod Docker images.&lt;/li&gt;
&lt;li&gt;Push a release to the CD pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mql1aCEp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqhuoxlo5urfo7adq9kd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mql1aCEp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqhuoxlo5urfo7adq9kd.png" alt="Screenshot of CI pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes, "Flaky Feature Tests" are present... but there is a concrete plan to eradicate them. This may even make its way into another article.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;p&gt;We use GitHub Actions, GitHub Workflows, and Github Packages. It ties in nicely with GitHub being our VCS of choice, and most importantly: it's simple. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity and explicitness&lt;/strong&gt; are key values we hold close.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 5-minute build
&lt;/h3&gt;

&lt;p&gt;One of the tenets to follow is to ensure builds in the CI pipeline are sub 10 minutes. At &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;, we've managed to keep our builds at around 5 minutes (that's just enough for me to brew my morning coffee... and maybe a few seconds to take that first sip).&lt;/p&gt;

&lt;p&gt;This allows for extremely rapid feedback to be informed when a build has failed, allowing our engineers to fix the problem and commit without ever having to twiddle their thumbs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Humans shouldn't do &lt;em&gt;machine things&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Everyone at &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt; is a talented human. So why would we ever want to waste their time doing mundane tasks they don't want to do? &lt;/p&gt;

&lt;p&gt;Well, we don't.&lt;/p&gt;

&lt;p&gt;We automate them, leaving our talented people to do the things they find interesting, the things that humans are better than machines at doing.&lt;/p&gt;

&lt;p&gt;That's why our unit tests, code style tests, feature tests, integration tests, and any other possible tests we decide to implement in the future are all automated.&lt;/p&gt;

&lt;p&gt;That means reviews left on PR's have no mention of code style or test coverage but focus purely on the human aspect. Things such as: if there may have been a more elegant way of implementing this or some design pattern that could have been leveraged, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CD pipeline
&lt;/h2&gt;

&lt;p&gt;The first thing to get out of the way (which I know you're all wondering): we mean "Continuous Delivery" when we talk about CD. Continuous Deployment is somewhere we'd like to get to, but for now, Continuous Delivery is where we're at.&lt;/p&gt;

&lt;p&gt;The goal of the CD pipeline is to deploy a release pushed by the CI pipeline to the specified environment.&lt;/p&gt;

&lt;p&gt;This is a single click-of-a-button affair that any of our engineers are trusted to do (Octopus Deploy is our tool of choice).&lt;/p&gt;

&lt;p&gt;There's not much else to say about this, as it is supposed to be simple by nature.&lt;/p&gt;

&lt;p&gt;There's scope to include further tests in the CD pipeline, such as deployment tests, smoke tests, canary deployments, etc.&lt;/p&gt;

&lt;p&gt;We will be implementing these supplementary automations down the line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qfLS_g7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3w5viriyk3kiuu6c3bf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qfLS_g7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3w5viriyk3kiuu6c3bf.png" alt="Screenshot of CD pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The entire workflow
&lt;/h2&gt;

&lt;p&gt;Let's take a step back. We've deep-dived into both our CI and CD pipeline, but now let's look at the entire workflow from commit to deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An engineer creates short-lived feature branch based off of &lt;code&gt;trunk/master/main&lt;/code&gt; for a ticket.&lt;/li&gt;
&lt;li&gt;The engineer makes a commit and pushes it to origin.&lt;/li&gt;
&lt;li&gt;The CI pipeline runs build.

&lt;ul&gt;
&lt;li&gt;If there's a failure, then the engineer is notified within 5 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;the CI pipeline creates and publishes a release to the CD pipeline.&lt;/li&gt;
&lt;li&gt;The release appears in the CD pipeline.&lt;/li&gt;
&lt;li&gt;The engineer can now deploy this release with a single-click to any environment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The summary
&lt;/h2&gt;

&lt;p&gt;I hope this gave you a good insight into how a CI/CD pipeline can be effectively implemented into a project.&lt;/p&gt;

&lt;p&gt;If you want to do some further reading into the topics covered in this article, I can highly recommend these books:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.co.uk/Continuous-Integration-Improving-Software-Signature/dp/0321336380"&gt;Continuous Integration: Improving Software Quality and Reducing Risk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.co.uk/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912"&gt;Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.co.uk/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339"&gt;Accelerate: The Science of Lean Software and Devops: Building and Scaling High Performing Technology Organizations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get in touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions, comment below, and I'll get back to you.&lt;/p&gt;

&lt;p&gt;And if you think you'd like to work somewhere like &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;, then message me for a chat at &lt;a href="//mailto:matt@healthplace.io"&gt;matt@healthplace.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@quinten149?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Quinten de Graaf&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/pipeline?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>healthplace</category>
      <category>ci</category>
      <category>cd</category>
      <category>automation</category>
    </item>
    <item>
      <title>Tech debt explained</title>
      <dc:creator>Matt Inamdar</dc:creator>
      <pubDate>Wed, 16 Jun 2021 22:43:09 +0000</pubDate>
      <link>https://dev.to/healthplace/tech-debt-explained-2gb5</link>
      <guid>https://dev.to/healthplace/tech-debt-explained-2gb5</guid>
      <description>&lt;p&gt;After a colleague I'm working with questioned me on "why we need to address tech debt", I found myself responding with a technical justification that helped nobody.&lt;/p&gt;

&lt;p&gt;After great reflection on that interaction, this article was born. I hope this helps you understand what tech debt is, how it's formed, and the implications of leaving it unaddressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The analogy
&lt;/h2&gt;

&lt;p&gt;Let's start with a familiar concept: the mighty combustion engine car.&lt;/p&gt;

&lt;p&gt;The year is 2021 and you've just purchased your new petrol car. This isn't just any car though, this car is special and you plan for it to last you at least 20 years.&lt;/p&gt;

&lt;p&gt;But wait! It's now the year 2030 and all of a sudden the combustion engine has been banned &lt;em&gt;shock and horror&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So you now decide to upgrade the car by making it electric (this is actually a thing).&lt;/p&gt;

&lt;p&gt;Here's the problem, you've only got 2 months before the combustion engine ban comes into effect, but the entire EV conversion is going to take at least 4 months.&lt;/p&gt;

&lt;p&gt;So what do you do? You cut corners...&lt;/p&gt;

&lt;p&gt;You remove the combustion engine and replace it with an electric motor and a battery. However, the important thing to note is that the rest of the mechanisms supporting the combustion engine remain; things such as the fuel tank and the exhaust pipe.&lt;/p&gt;

&lt;p&gt;You're now driving this hybrid Frankenstein around and loving life. But disaster strikes! Global warming has caused temperatures to rise and now you're sweltering in your iron tomb of a car with no hopes of happiness.&lt;/p&gt;

&lt;p&gt;So you take it to the garage to get a sweet new AC system installed.&lt;/p&gt;

&lt;p&gt;The problem is, the mechanic has quoted you 3 times the usual estimate, simply because it's going to be a pain in the arse to navigate around this beast. She also notices that your fuel tank which you left from your half-completed job has started leaking onto the battery.&lt;/p&gt;

&lt;p&gt;By this time it's too late, the cost to get this sorted is going to require you to remortgage your house...&lt;/p&gt;

&lt;p&gt;You should have completed the job sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The summary
&lt;/h2&gt;

&lt;p&gt;Hopefully you enjoyed this epic novel and it made the story of tech debt more relatable.&lt;/p&gt;

&lt;p&gt;For the sake of clarity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The car is your product&lt;/li&gt;
&lt;li&gt;The combustion engine/mechanism is your existing functionality that needs to be decommissioned&lt;/li&gt;
&lt;li&gt;The electric motor/battery is your new functionality&lt;/li&gt;
&lt;li&gt;The mechanic is your developers&lt;/li&gt;
&lt;li&gt;Global warming is real&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get in touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions, comment below, and I'll get back to you.&lt;/p&gt;

&lt;p&gt;And if you think you'd like to work somewhere like &lt;a href="https://healthplace.io"&gt;Health Place&lt;/a&gt;, then message me for a chat at &lt;a href="//mailto:matt@healthplace.io"&gt;matt@healthplace.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@eddiecreates?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Eddie Jones&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/car-garage?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>healthplace</category>
      <category>techdebt</category>
    </item>
  </channel>
</rss>
