<?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: Linh Nguyen</title>
    <description>The latest articles on DEV Community by Linh Nguyen (@tuleism).</description>
    <link>https://dev.to/tuleism</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%2F453484%2Fff991142-61ae-44d6-abd4-49eff5700f6e.png</url>
      <title>DEV Community: Linh Nguyen</title>
      <link>https://dev.to/tuleism</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tuleism"/>
    <language>en</language>
    <item>
      <title>OpenTelemetry Distributed Tracing with ZIO</title>
      <dc:creator>Linh Nguyen</dc:creator>
      <pubDate>Mon, 01 Nov 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/tuleism/opentelemetry-distributed-tracing-with-zio-45di</link>
      <guid>https://dev.to/tuleism/opentelemetry-distributed-tracing-with-zio-45di</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This post is some quick notes on using &lt;a href="https://zio.dev/"&gt;ZIO&lt;/a&gt; and &lt;a href="https://github.com/zio/zio-telemetry"&gt;zio-telemetry&lt;/a&gt; to implement &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry&lt;/a&gt; distributed tracing for Scala applications.&lt;br&gt;
The source code is available &lt;a href="https://github.com/tuleism/opentelemetry-distributed-tracing-zio"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is not an introduction to any of these technologies, but here are a few good reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NewRelic's introduction to &lt;a href="https://newrelic.com/resources/ebooks/quick-introduction-distributed-tracing"&gt;Distributed Tracing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Lightstep's concise summary for &lt;a href="https://opentelemetry.lightstep.com/"&gt;OpenTelemetry&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Initial implementation
&lt;/h2&gt;

&lt;p&gt;For demonstration purpose, we will perform &lt;a href="https://opentelemetry.io/docs/concepts/instrumenting/#manual-instrumentation"&gt;manual instrumentation&lt;/a&gt; on a &lt;a&gt;modified&lt;/a&gt; version of the &lt;a href="https://scalapb.github.io/zio-grpc/"&gt;zio-grpc&lt;/a&gt;'s &lt;a href="https://github.com/scalapb/zio-grpc/tree/master/examples/helloworld"&gt;helloworld example&lt;/a&gt;, in which we incorporate both &lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt; and HTTP communications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Original&lt;/strong&gt;: &lt;code&gt;hello-client&lt;/code&gt; sends a &lt;code&gt;HelloRequest&lt;/code&gt; with &lt;code&gt;name&lt;/code&gt; &lt;em&gt;x&lt;/em&gt; and &lt;code&gt;hello-server&lt;/code&gt; returns a &lt;code&gt;HelloResponse&lt;/code&gt; with &lt;code&gt;message&lt;/code&gt; &lt;em&gt;Hello, x&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modified&lt;/strong&gt;: in addition to the original behavior, client sends an optional integer field &lt;code&gt;guess&lt;/code&gt; and server performs an HTTP request to &lt;a href="https://httpbin.org/"&gt;HTTPBin&lt;/a&gt; based on its value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mndFXMvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnqh607w1uvszevnsdz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mndFXMvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnqh607w1uvszevnsdz4.png" alt="Initial Diagram" width="631" height="127"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Add the new flag &lt;code&gt;guess&lt;/code&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The greeting service definition.&lt;/span&gt;
&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;Greeter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Sends a greeting&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;SayHello&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloReply&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="c1"&gt;// The request message containing the user's name.&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;HelloRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&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="n"&gt;google.protobuf.Int32Value&lt;/span&gt; &lt;span class="na"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The response message containing the greetings&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;HelloReply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="kd"&gt;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add zio-grpc dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="n"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;Resolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sonatypeRepo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"snapshots"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;addSbtPlugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.scalameta"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"sbt-scalafmt"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"2.4.3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;addSbtPlugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.thesamet"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"sbt-protoc"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"1.0.4"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioGrpcVersion&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.5.1+12-93cdbe22-SNAPSHOT"&lt;/span&gt;

&lt;span class="n"&gt;libraryDependencies&lt;/span&gt; &lt;span class="o"&gt;++=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"com.thesamet.scalapb.zio-grpc"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-grpc-codegen"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioGrpcVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"com.thesamet.scalapb"&lt;/span&gt;          &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"compilerplugin"&lt;/span&gt;   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"0.11.5"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set up &lt;code&gt;build.sbt&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Generate Scala code from &lt;code&gt;helloworld.proto&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Depend on &lt;a href="https://sttp.softwaremill.com"&gt;sttp&lt;/a&gt; for HTTP client.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;grpcVersion&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.41.0"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;sttpVersion&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.3.15"&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;scalaPBRuntime&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.thesamet.scalapb"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"scalapb-runtime-grpc"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;scalapb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;scalapbVersion&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;grpcRuntimeDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"io.grpc"&lt;/span&gt;      &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"grpc-netty"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;grpcVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;scalaPBRuntime&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;scalaPBRuntime&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"protobuf"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;sttpZioDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"com.softwaremill.sttp.client3"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"async-http-client-backend-zio"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;sttpVersion&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;lazy&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"opentelemetry-distributed-tracing-zio"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="py"&gt;aggregate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;lazy&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;commonProject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zio"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;Compile&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;PB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;scalapb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;gen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Compile&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sourceManaged&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;scalapb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;zio_grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ZioCodeGenerator&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Compile&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sourceManaged&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt;
  &lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;libraryDependencies&lt;/span&gt; &lt;span class="o"&gt;++=&lt;/span&gt; &lt;span class="n"&gt;grpcRuntimeDeps&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="n"&gt;sttpZioDeps&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a gRPC client pointing to localhost:9000.&lt;/li&gt;
&lt;li&gt;Send a single &lt;code&gt;HelloRequest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Send 5 &lt;code&gt;HelloRequest&lt;/code&gt;s in parallel.&lt;/li&gt;
&lt;li&gt;Send a single &lt;code&gt;HelloRequest&lt;/code&gt; with an invalid guess.&lt;/li&gt;
&lt;li&gt;Print "Done" and exit.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZClient&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;clientLayer&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ZManagedChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;ManagedChannelBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;forAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;usePlaintext&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;singleHello&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;multipleHellos&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;collectAllParN&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
      &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
      &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
      &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
      &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;invalidHello&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))).&lt;/span&gt;&lt;span class="py"&gt;ignore&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;myAppLogic&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;singleHello&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;multipleHellos&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;invalidHello&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;putStrLn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZEnv&lt;/span&gt;, &lt;span class="kt"&gt;ExitCode&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;myAppLogic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;provideCustomLayer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientLayer&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;exitCode&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fail the request if &lt;code&gt;guess&lt;/code&gt; is less than 0.&lt;/li&gt;
&lt;li&gt;Based on the value of &lt;code&gt;guess&lt;/code&gt;, delay for some time and then send a request to HTTPBin.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;ZGreeterEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Clock&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Random&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;SttpClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZGreeterImpl&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;RGreeter&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZGreeterEnv&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZGreeterEnv&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;HelloReply&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;guess&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getOrElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;_&lt;/span&gt;      &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;INVALID_ARGUMENT&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;code&lt;/span&gt;   &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="o"&gt;???&lt;/span&gt;
      &lt;span class="n"&gt;delayMs&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;???&lt;/span&gt;
      &lt;span class="k"&gt;_&lt;/span&gt;      &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;httpRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;delayMs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;millis&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mapError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;INTERNAL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;withCause&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;HelloReply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"Hello, ${request.name}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;httpRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SttpClient&lt;/span&gt;, &lt;span class="kt"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;basicRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="s"&gt;"https://httpbin.org/status/$code"&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="py"&gt;unit&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run it
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To run the server:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sbt &lt;span class="s2"&gt;"zio/runMain com.github.tuleism.ZServer"&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;info] running &lt;span class="o"&gt;(&lt;/span&gt;fork&lt;span class="o"&gt;)&lt;/span&gt; com.github.tuleism.ZServer
&lt;span class="o"&gt;[&lt;/span&gt;info] Server is running. Press Ctrl-C to stop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To run the client:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sbt &lt;span class="s2"&gt;"zio/runMain com.github.tuleism.ZClient"&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;info] running &lt;span class="o"&gt;(&lt;/span&gt;fork&lt;span class="o"&gt;)&lt;/span&gt; com.github.tuleism.ZClient
&lt;span class="o"&gt;[&lt;/span&gt;info] Done
&lt;span class="o"&gt;[&lt;/span&gt;success] Total &lt;span class="nb"&gt;time&lt;/span&gt;: 12 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we only know that it takes roughly 12 seconds for the client to initialize and finish its work.&lt;/p&gt;

&lt;p&gt;Let's add distributed tracing to gain more insights into this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common tracing requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For both client and server, we need to acquire a &lt;a href=""&gt;Tracer&lt;/a&gt;, an object responsible for creating and managing &lt;a href=""&gt;Spans&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Tracing data is sent to &lt;a href="https://www.jaegertracing.io/"&gt;Jaeger&lt;/a&gt;, which acts as a standalone &lt;a href="https://opentelemetry.lightstep.com/the-collector-and-exporters/"&gt;collector&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jxViXLDd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oug7wvdrcos2luchiq3e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jxViXLDd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oug7wvdrcos2luchiq3e.png" alt="Instrumented Diagram" width="631" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add new dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://zio.github.io/zio-telemetry/docs/overview/overview_opentelemetry"&gt;zio-telemetry's OpenTelemetry module&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We also depend on &lt;a href="https://zio.github.io/zio-config/"&gt;zio-config&lt;/a&gt; to read tracing config from file and &lt;a href="https://github.com/kitlangton/zio-magic"&gt;zio-magic&lt;/a&gt; to ease &lt;a href="https://zio.dev/1.x/datatypes/contextual/zlayer/"&gt;ZLayer&lt;/a&gt; wiring.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;openTelemetryVersion&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.6.0"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioConfigVersion&lt;/span&gt;     &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.10"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioMagicVersion&lt;/span&gt;      &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3.9"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioTelemetryVersion&lt;/span&gt;  &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8.2"&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;openTelemetryDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"io.opentelemetry"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"opentelemetry-exporter-jaeger"&lt;/span&gt;    &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;openTelemetryVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"io.opentelemetry"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"opentelemetry-sdk"&lt;/span&gt;                &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;openTelemetryVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"io.opentelemetry"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;"opentelemetry-extension-noop-api"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"$openTelemetryVersion-alpha"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioConfigDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"dev.zio"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-config"&lt;/span&gt;          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioConfigVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"dev.zio"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-config-magnolia"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioConfigVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"dev.zio"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-config-typesafe"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioConfigVersion&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioMagicDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"io.github.kitlangton"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-magic"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioMagicVersion&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;zioTelemetryDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"dev.zio"&lt;/span&gt;                       &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-opentelemetry"&lt;/span&gt;                   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;zioTelemetryVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"com.softwaremill.sttp.client3"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"zio-telemetry-opentelemetry-backend"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;sttpVersion&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a config layer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tracing {
  enable = false
  enable = ${?TRACING_ENABLE}
  endpoint = "http://127.0.0.1:14250"
  endpoint = ${?JAEGER_ENDPOINT}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TracingConfig&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TracingConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;AppConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;configDescriptor&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;live&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ReadError&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;TypesafeConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fromDefaultLoader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configDescriptor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a &lt;code&gt;Tracer&lt;/code&gt; layer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Depend on the configuration, we either create a &lt;em&gt;noop&lt;/em&gt; &lt;code&gt;Tracer&lt;/code&gt; or one that sends data to Jaeger.&lt;/li&gt;
&lt;li&gt;Once we have it, we can construct a &lt;code&gt;Tracing&lt;/code&gt; layer, which give us access to many &lt;a href="https://www.javadoc.io/static/dev.zio/zio-opentelemetry_2.13/0.8.2/zio/telemetry/opentelemetry/Tracing%24.html"&gt;useful operations in zio-telemetry&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZTracer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;InstrumentationName&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.github.tuleism"&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;managed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;resource&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ResourceAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;spanExporter&lt;/span&gt;   &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZManaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fromAutoCloseable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                          &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;JaegerGrpcSpanExporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;setEndpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                        &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;spanProcessor&lt;/span&gt;  &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZManaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fromAutoCloseable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UIO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spanExporter&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
      &lt;span class="n"&gt;tracerProvider&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;UIO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                          &lt;span class="nv"&gt;SdkTracerProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;addSpanProcessor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spanProcessor&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;setResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;toManaged_&lt;/span&gt;
      &lt;span class="n"&gt;openTelemetry&lt;/span&gt;  &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;UIO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OpenTelemetrySdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;setTracerProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracerProvider&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="py"&gt;toManaged_&lt;/span&gt;
      &lt;span class="n"&gt;tracer&lt;/span&gt;         &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;UIO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;openTelemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getTracer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InstrumentationName&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="py"&gt;toManaged_&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;live&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RLayer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;TracingConfig&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracer&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;TracingConfig&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="py"&gt;toManaged_&lt;/span&gt;
        &lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;NoopOpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;getTracer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InstrumentationName&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="py"&gt;toManaged_&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;managed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;
    &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;toLayer&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Instrument the HTTP client
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use out-of-the-box &lt;a href="https://sttp.softwaremill.com/en/latest/backends/wrappers/zio-opentelemetry.html"&gt;sttp backend&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We also add additional HTTP specific attributes according to the OpenTelemetry's &lt;a href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name"&gt;semantic convention&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;SttpTracing&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;wrapper&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ZioTelemetryOpenTelemetryTracer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;, &lt;span class="kt"&gt;Nothing&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing&lt;/span&gt;, &lt;span class="kt"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;HTTP_METHOD&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;method&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;
        &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;HTTP_URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;
        &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;unit&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing&lt;/span&gt;, &lt;span class="kt"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;HTTP_STATUS_CODE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;code&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;code&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;
        &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;unit&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;live&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;AsyncHttpClientZioBackend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;flatMap&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;hasBackend&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;ZIO&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing.Service&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;ZioTelemetryOpenTelemetryBackend&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;hasBackend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toLayer&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Instrument the gRPC server
&lt;/h3&gt;

&lt;p&gt;We can add &lt;code&gt;Tracing&lt;/code&gt; without changing our server implementation with a &lt;a href="https://scalapb.github.io/zio-grpc/docs/decorating/"&gt;ZTransform&lt;/a&gt;. For each request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;a href="https://www.javadoc.io/static/dev.zio/zio-opentelemetry_2.13/0.8.2/zio/telemetry/opentelemetry/TracingSyntax%24%24OpenTelemetryZioOps.html#spanFrom%5BC%5D(propagator:io.opentelemetry.context.propagation.TextMapPropagator,carrier:C,getter:io.opentelemetry.context.propagation.TextMapGetter%5BC%5D,spanName:String,spanKind:io.opentelemetry.api.trace.SpanKind,toErrorStatus:PartialFunction%5BE,io.opentelemetry.api.trace.StatusCode%5D):zio.ZIO%5BRwithzio.telemetry.opentelemetry.Tracing,E,A%5D"&gt;zio-telemetry's spanFrom&lt;/a&gt;, which extracts the propagated context (through &lt;a href="https://grpc.io/docs/what-is-grpc/core-concepts/#metadata"&gt;gRPC Metadata&lt;/a&gt;, using &lt;a href="https://w3c.github.io/trace-context/"&gt;W3C Trace Context format&lt;/a&gt;) and starts a new child &lt;code&gt;Span&lt;/code&gt; right after.&lt;/li&gt;
&lt;li&gt;We have access to a &lt;a href="https://scalapb.github.io/zio-grpc/docs/context"&gt;RequestContext&lt;/a&gt; and thus the full method name used for &lt;code&gt;Span&lt;/code&gt;'s name.&lt;/li&gt;
&lt;li&gt;We also add additional gRPC specific attributes according to the OpenTelemetry's &lt;a href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#grpc-attributes"&gt;semantic convention&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;GrpcTracing&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;propagator&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TextMapPropagator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;W3CTraceContextPropagator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;metadataGetter&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TextMapGetter&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextMapGetter&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;java.lang.Iterable&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;carrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;carrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ASCII_STRING_MARSHALLER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;withSemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;effect&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;RPC_SYSTEM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"grpc"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;effect&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;tapBoth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;RPC_GRPC_STATUS_CODE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;RPC_GRPC_STATUS_CODE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serverTracingTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ZTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;rc&lt;/span&gt;       &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
          &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;result&lt;/span&gt;   &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;withSemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;spanFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                          &lt;span class="n"&gt;propagator&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;metadataGetter&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                          &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;methodDescriptor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getFullMethodName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                          &lt;span class="nv"&gt;SpanKind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;SERVER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                          &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ERROR&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZStream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZStream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
        &lt;span class="o"&gt;???&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Server Main
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add required layers for Tracing.&lt;/li&gt;
&lt;li&gt;Transform the original &lt;code&gt;ZGreeterImpl&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;zio.magic._&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZServer&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServerMain&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;requirements&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;ZLayer&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;wire&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZEnv&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;ZGreeterEnv&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;
        &lt;span class="nv"&gt;ZEnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;narrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;ZTracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello-server"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;SttpTracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;orDie&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;services&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ServiceList&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;ServiceList&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ZGreeterImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZGreeterEnv&lt;/span&gt;, &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;]](&lt;/span&gt;&lt;span class="nv"&gt;GrpcTracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;serverTracingTransform&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;provideLayer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New client
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inject current context into &lt;a href="https://grpc.io/docs/what-is-grpc/core-concepts/#metadata"&gt;gRPC Metadata&lt;/a&gt; for &lt;a href="https://opentelemetry.lightstep.com/core-concepts/context-propagation/"&gt;context propagation&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;GrpcTracing&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;metadataSetter&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TextMapSetter&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;carrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ASCII_STRING_MARSHALLER&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;contextPropagationClientInterceptor&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZClientInterceptor&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ZClientInterceptor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;headersUpdater&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nv"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;wrapM&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;inject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagator&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadataSetter&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZClient&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;clientLayer&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;GreeterClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ZManagedChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;ManagedChannelBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;forAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;usePlaintext&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
      &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;GrpcTracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;contextPropagationClientInterceptor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start a &lt;code&gt;Span&lt;/code&gt; for each request
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;ZTransform&lt;/code&gt; to record the relevant gRPC attributes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;GrpcTracing&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clientTracingTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ZTransform&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSemanticAttributes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZStream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZStream&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;R&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt;, &lt;span class="kt"&gt;Status&lt;/span&gt;, &lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;???&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Unlike the server, we don't have access to a &lt;code&gt;RequestContext&lt;/code&gt; object, so we have to set the method name manually.&lt;/li&gt;
&lt;li&gt;We also start additional &lt;code&gt;Span&lt;/code&gt;s.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZClient&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;


  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errorToStatusCode&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;E&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PartialFunction&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;E&lt;/span&gt;, &lt;span class="kt"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ERROR&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;GreeterClient&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;span&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;GreeterGrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;METHOD_SAY_HELLO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getFullMethodName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;SpanKind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;CLIENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;errorToStatusCode&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;singleHello&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;span&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"singleHello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toErrorStatus&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errorToStatusCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;multipleHellos&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZIO&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;collectAllParN&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;
      &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
        &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
        &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
        &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;))),&lt;/span&gt;
        &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;span&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multipleHellos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toErrorStatus&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errorToStatusCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;invalidHello&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))).&lt;/span&gt;&lt;span class="py"&gt;ignore&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;span&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalidHello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toErrorStatus&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errorToStatusCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add required layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZClient&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;requirements&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZLayer&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;wire&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZEnv&lt;/span&gt; &lt;span class="kt"&gt;with&lt;/span&gt; &lt;span class="kt"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;
      &lt;span class="nv"&gt;ZEnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;narrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nv"&gt;ZTracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello-client"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;live&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;+&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clientLayer&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URIO&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ZEnv&lt;/span&gt;, &lt;span class="kt"&gt;ExitCode&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;myAppLogic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;provideCustomLayer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;exitCode&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Showtime
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Run Jaeger through Docker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tracing data can be sent to port 14250.&lt;/li&gt;
&lt;li&gt;We can view &lt;a href="https://github.com/jaegertracing/jaeger-ui"&gt;Jaeger UI&lt;/a&gt; at &lt;a href="http://localhost:16686"&gt;http://localhost:16686&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; jaeger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 16686:16686 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 14250:14250 &lt;span class="se"&gt;\&lt;/span&gt;
  jaegertracing/all-in-one:1.25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start the server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ TRACING_ENABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;sbt &lt;span class="s2"&gt;"zio/runMain com.github.tuleism.ZServer"&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;info] running &lt;span class="o"&gt;(&lt;/span&gt;fork&lt;span class="o"&gt;)&lt;/span&gt; com.github.tuleism.ZServer
&lt;span class="o"&gt;[&lt;/span&gt;info] Server is running. Press Ctrl-C to stop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start the client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ TRACING_ENABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;sbt &lt;span class="s2"&gt;"zio/runMain com.github.tuleism.ZClient"&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;info] running &lt;span class="o"&gt;(&lt;/span&gt;fork&lt;span class="o"&gt;)&lt;/span&gt; com.github.tuleism.ZClient
&lt;span class="o"&gt;[&lt;/span&gt;info] Done
&lt;span class="o"&gt;[&lt;/span&gt;success] Total &lt;span class="nb"&gt;time&lt;/span&gt;: 12 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Distributed Tracing in action!
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Now we can see the details for &lt;code&gt;multipleHellos&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n60aoHBS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j54n9vrfngkjb6idaddh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n60aoHBS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j54n9vrfngkjb6idaddh.png" alt="multipleHellos Span" width="880" height="361"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;And which &lt;code&gt;guess&lt;/code&gt; is causing the longest delay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cQn3HSND--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ojdo73cv5o525j3inn2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cQn3HSND--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ojdo73cv5o525j3inn2b.png" alt="bad guess" width="880" height="193"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with Logging
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Let's add tracing context into log messages following the &lt;a href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/overview.md#trace-context-in-legacy-formats"&gt;specification&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We're going to use &lt;a href="https://izumi.7mind.io/logstage/index.html"&gt;izumi logstage&lt;/a&gt;, our favorite logging library.&lt;/li&gt;
&lt;li&gt;See the &lt;a href="https://github.com/tuleism/opentelemetry-distributed-tracing-zio/commit/d9ba391dc9725e3e4ddae9c05cfe2641d8a435cf"&gt;diffs&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add logging dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;izumiVersion&lt;/span&gt;         &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.8"&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;loggingDeps&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"io.7mind.izumi"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"logstage-core"&lt;/span&gt;          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;izumiVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"io.7mind.izumi"&lt;/span&gt; &lt;span class="o"&gt;%%&lt;/span&gt; &lt;span class="s"&gt;"logstage-adapter-slf4j"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;izumiVersion&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup logging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;trace_id&lt;/code&gt;, &lt;code&gt;span_id&lt;/code&gt; to logging context if current trace context is valid.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Logging&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;baseLogger&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IzLogger&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;live&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ZLayer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing.Service&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="kt"&gt;Nothing&lt;/span&gt;, &lt;span class="kt"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;LogZIO.Service&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;ZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Tracing.Service&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;LogZIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;withDynamicContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseLogger&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;
        &lt;span class="nv"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getCurrentSpanContext&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spanContext&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spanContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;isValid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="nc"&gt;CustomContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"trace_id"&lt;/span&gt;    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;spanContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getTraceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"span_id"&lt;/span&gt;     &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;spanContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getSpanId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"trace_flags"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;spanContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getTraceFlags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asHex&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
              &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
              &lt;span class="nv"&gt;CustomContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;empty&lt;/span&gt;
          &lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;toLayer&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a few log messages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;E.g for &lt;code&gt;singleHello&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ZClient&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nv"&gt;zio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;singleHello&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"singleHello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;span&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"singleHello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toErrorStatus&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errorToStatusCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sample Logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;info] running &lt;span class="o"&gt;(&lt;/span&gt;fork&lt;span class="o"&gt;)&lt;/span&gt; com.github.tuleism.ZClient
&lt;span class="o"&gt;[&lt;/span&gt;info] I 2021-11-01T22:59:10.881 &lt;span class="o"&gt;(&lt;/span&gt;ZClient.scala:37&lt;span class="o"&gt;)&lt;/span&gt;  …tuleism.ZClient.singleHello &lt;span class="o"&gt;[&lt;/span&gt;24:zio-default-async-11] &lt;span class="nv"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9c8a7ebb87381293bc8937a5f7673cb9, &lt;span class="nv"&gt;span_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cb7c9a440472e1be, &lt;span class="nv"&gt;trace_flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01 singleHello
&lt;span class="o"&gt;[&lt;/span&gt;info] I 2021-11-01T22:59:14.064 &lt;span class="o"&gt;(&lt;/span&gt;ZClient.scala:44&lt;span class="o"&gt;)&lt;/span&gt;  …eism.ZClient.multipleHellos &lt;span class="o"&gt;[&lt;/span&gt;21:zio-default-async-8 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fe405246fbaa5f876c19f14fa649a99f, &lt;span class="nv"&gt;span_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bef19494bef4106e, &lt;span class="nv"&gt;trace_flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01 multipleHellos
&lt;span class="o"&gt;[&lt;/span&gt;info] I 2021-11-01T22:59:18.171 &lt;span class="o"&gt;(&lt;/span&gt;ZClient.scala:60&lt;span class="o"&gt;)&lt;/span&gt;  …uleism.ZClient.invalidHello &lt;span class="o"&gt;[&lt;/span&gt;26:zio-default-async-13] &lt;span class="nv"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;be5ccd425e0cfb01fd97274abd0c4d72, &lt;span class="nv"&gt;span_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ea6499fb9a7c8d28, &lt;span class="nv"&gt;trace_flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01 invalidHello
&lt;span class="o"&gt;[&lt;/span&gt;info] I 2021-11-01T22:59:18.272 &lt;span class="o"&gt;(&lt;/span&gt;ZClient.scala:66&lt;span class="o"&gt;)&lt;/span&gt;  ….tuleism.ZClient.myAppLogic &lt;span class="o"&gt;[&lt;/span&gt;15:zio-default-async-2 &lt;span class="o"&gt;]&lt;/span&gt; Done
&lt;span class="o"&gt;[&lt;/span&gt;success] Total &lt;span class="nb"&gt;time&lt;/span&gt;: 12 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extra notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If we receive an HTTP 5xx response, we should set the &lt;code&gt;Span&lt;/code&gt; status to error according to the &lt;a href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status"&gt;semantic convention&lt;/a&gt;. However, it is currently &lt;a href="https://github.com/zio/zio-telemetry/issues/444"&gt;not possible&lt;/a&gt; with &lt;code&gt;zio-telemetry&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We need &lt;a href="https://github.com/scalapb/zio-grpc/issues/309"&gt;a better way&lt;/a&gt; to implement tracing for &lt;code&gt;zio-grpc&lt;/code&gt; client.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>distributedtracing</category>
      <category>observability</category>
      <category>scala</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Parallel, Back-pressured Kafka Consumer</title>
      <dc:creator>Linh Nguyen</dc:creator>
      <pubDate>Tue, 14 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/tuleism/parallel-back-pressured-kafka-consumer-2972</link>
      <guid>https://dev.to/tuleism/parallel-back-pressured-kafka-consumer-2972</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.confluent.io/learn/kafka-tutorial/"&gt;Almost&lt;/a&gt; &lt;a href="http://cloudurable.com/blog/kafka-tutorial-kafka-consumer/index.html"&gt;every&lt;/a&gt; &lt;a href="https://www.oreilly.com/library/view/kafka-the-definitive/9781491936153/ch04.html"&gt;Kafka Consumer tutorial&lt;/a&gt; structure their code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;KafkaConsumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;KafkaConsumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Subscribe to Kafka topics&lt;/span&gt;
&lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topics&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Poll Kafka for new messages&lt;/span&gt;
    &lt;span class="nc"&gt;ConsumerRecords&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;poll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Processing logic&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsumerRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically we set up our &lt;a href="https://javadoc.io/doc/org.apache.kafka/kafka-clients/latest/org/apache/kafka/clients/consumer/KafkaConsumer.html"&gt;Kafka Consumer&lt;/a&gt;, &lt;code&gt;subscribe&lt;/code&gt; it to some &lt;a href="https://dattell.com/data-architecture-blog/what-is-a-kafka-topic/"&gt;Kafka topics&lt;/a&gt; and then go into an infinite loop where, on each iteration, we &lt;code&gt;poll&lt;/code&gt; some messages from these topics and process them one by one. We can call this the &lt;strong&gt;poll-then-process loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is fairly simple, easy to put into practice, and people may have been using it in production without any issue. However, there are various problems with this model, which we're going into more details in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems with the poll-then-process loop
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. It is not the "expected" way to poll
&lt;/h3&gt;

&lt;p&gt;Looking at the code above, we developers might think that &lt;code&gt;poll&lt;/code&gt; acts as a way to signal &lt;strong&gt;demand&lt;/strong&gt; to Kafka. Our consumer only &lt;code&gt;poll&lt;/code&gt;s to pull in more messages when it has finished working on previous ones. If its processing rate is slow, Kafka would act as the &lt;strong&gt;shock absorber&lt;/strong&gt;, ensuring we don't lose any message even when the producing rate is much higher.&lt;/p&gt;

&lt;p&gt;On the other hand, when processing rate is slow, the &lt;strong&gt;interval&lt;/strong&gt; between consecutive &lt;code&gt;poll&lt;/code&gt;s also &lt;strong&gt;increases&lt;/strong&gt;. This is problematic since there is a default (5 minutes) upper bound on it with the &lt;a href="https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_max.poll.interval.ms"&gt;max.poll.interval.ms&lt;/a&gt; configuration:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;max.poll.interval.ms&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The maximum delay between invocations of poll() when using consumer group management. This places an upper bound on the amount of time that the consumer can be idle before fetching more records. If poll() is not called before expiration of this timeout, then the consumer is considered failed and the group will rebalance in order to reassign the partitions to another member. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, if our consumer doesn't call &lt;code&gt;poll&lt;/code&gt; at least once every &lt;code&gt;max.poll.interval.ms&lt;/code&gt;, to Kafka, it's as good as dead. When this happens, Kafka follows up with a &lt;a href="https://medium.com/streamthoughts/apache-kafka-rebalance-protocol-or-the-magic-behind-your-streams-applications-e94baf68e4f2"&gt;rebalance&lt;/a&gt; process to distribute the current work of the dead consumer to the rest of its &lt;a href="https://docs.confluent.io/platform/current/clients/consumer.html#consumer-groups"&gt;consumer group&lt;/a&gt;. This introduces more overhead and delay into an already slow processing rate.&lt;/p&gt;

&lt;p&gt;Worse yet, if the processing causes slowness in one consumer, chances are, it would cause the same problem for other consumers taking over its work. Moreover, the presumed dead consumer can also cause &lt;strong&gt;rebalance&lt;/strong&gt; when it attempts to rejoin the group on its next poll (remember it's an &lt;em&gt;infinite&lt;/em&gt; loop!). Both of these makes &lt;strong&gt;rebalance&lt;/strong&gt; happen again and again, slowing consumption even more.&lt;/p&gt;

&lt;p&gt;Now, there is another configuration which can help with this situation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;max.poll.records&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The maximum number of records returned in a single call to poll(). Note, that max.poll.records does not impact the underlying fetching behavior. The consumer will cache the records from each fetch request and returns them incrementally from each poll.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this set to a lower value, our consumer will process fewer messages per &lt;code&gt;poll&lt;/code&gt;. Thus, the &lt;code&gt;poll&lt;/code&gt; interval will decrease. Alternatively, we can also increase &lt;code&gt;max.poll.interval.ms&lt;/code&gt; to a bigger value. This should solve the problem temporarily if we can't move away from the &lt;strong&gt;poll-then-process&lt;/strong&gt; loop. Nevertheless, it is not ideal.&lt;/p&gt;

&lt;p&gt;Firstly, these configurations are set when we start our consumers, but whether they work or not depends on the messages or the applications. We may set them specifically for each application, but in the end, we're playing a &lt;strong&gt;guessing game&lt;/strong&gt; and pray that we're lucky.&lt;/p&gt;

&lt;p&gt;Secondly, in the worst case, it may take &lt;strong&gt;twice&lt;/strong&gt; the &lt;code&gt;max.poll.interval.ms&lt;/code&gt; duration for the &lt;strong&gt;rebalance&lt;/strong&gt; process to start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kafka has to wait &lt;code&gt;max.poll.interval.ms&lt;/code&gt; to &lt;strong&gt;detect&lt;/strong&gt; that our consumer is not &lt;code&gt;poll&lt;/code&gt;ing anymore.&lt;/li&gt;
&lt;li&gt;When Kafka decides to &lt;strong&gt;rebalance&lt;/strong&gt; the group, other consumers are only &lt;strong&gt;made aware&lt;/strong&gt; of this decision on their &lt;strong&gt;next&lt;/strong&gt; &lt;code&gt;poll&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We never want &lt;strong&gt;rebalance&lt;/strong&gt; to take even more time, so setting a higher &lt;code&gt;max.poll.interval.ms&lt;/code&gt; is not great.&lt;/p&gt;

&lt;p&gt;Finally, these configurations implies that our consumer is "expected" to &lt;code&gt;poll&lt;/code&gt; frequently, at least once every &lt;code&gt;max.poll.interval.ms&lt;/code&gt;, no matter what kind of processing it is doing. By not incorporating this expectation, the &lt;strong&gt;poll-then-process&lt;/strong&gt; loop is not only &lt;strong&gt;misleading&lt;/strong&gt; to developers but also doomed to fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Message processing is synchronous
&lt;/h3&gt;

&lt;p&gt;Kafka only guarantees the &lt;strong&gt;order&lt;/strong&gt; of messages &lt;strong&gt;within one partition&lt;/strong&gt;. Messages from different partitions are unrelated and can be processed in parallel. That's why in Kafka, the &lt;strong&gt;number of partition&lt;/strong&gt; in a topic is the unit of &lt;strong&gt;parallelism&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In theory, we could easily achieve maximum parallelism by having &lt;strong&gt;as many&lt;/strong&gt; consumers running as the number of partitions on a topic. However, in reality, this is too much overhead, not to mention its impact on increasing the chance for &lt;strong&gt;rebalance&lt;/strong&gt;, since there are more consumers that can come and go.&lt;/p&gt;

&lt;p&gt;If we look at our consumer code again, it can subscribe to multiple topics and possibly receive messages from multiple partitions. Yet, when it comes to processing these messages, it does so one by one. This is not optimal.&lt;/p&gt;

&lt;p&gt;Now, assuming our processing logic is very simple, could we just use a &lt;a href="https://en.wikipedia.org/wiki/Thread_pool"&gt;thread pool&lt;/a&gt; to parallelize it? For example, by submitting one processing task to the thread pool, for each message?&lt;/p&gt;

&lt;p&gt;Well, kind of. It only works if we don't care about processing &lt;strong&gt;ordering&lt;/strong&gt; and &lt;strong&gt;guarantees&lt;/strong&gt; like &lt;em&gt;at-most-once&lt;/em&gt;, &lt;em&gt;at-least-once&lt;/em&gt;, etc. So it is not very useful in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better model
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;The many setbacks of the &lt;strong&gt;poll-then-process&lt;/strong&gt; loop come from the fact that different concerns - &lt;code&gt;poll&lt;/code&gt;ing, processing, offsets committing - are mixed together. As a result, when we &lt;strong&gt;divide&lt;/strong&gt; them into separated components, we end up with an improved model which supports &lt;strong&gt;parallel&lt;/strong&gt; processing and &lt;strong&gt;back-pressure&lt;/strong&gt; properly. Each component is described in more details below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6zTRa1S3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7h7j4y1t3xubi94qiv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6zTRa1S3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7h7j4y1t3xubi94qiv6.png" alt="Parallel Kafka Consumer"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Work Queues
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Work Queues&lt;/strong&gt; is the communication channel between &lt;strong&gt;Poller&lt;/strong&gt; and &lt;strong&gt;Executor&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a &lt;em&gt;one-to-one&lt;/em&gt; mapping of assigned &lt;a href=""&gt;TopicPartition&lt;/a&gt;s to &lt;strong&gt;work queues&lt;/strong&gt;. After each &lt;code&gt;poll&lt;/code&gt;, &lt;strong&gt;Poller&lt;/strong&gt; pushes new messages from each partition into its corresponding work queue, &lt;strong&gt;preserving&lt;/strong&gt; the original &lt;strong&gt;ordering&lt;/strong&gt;. Each work queue is also &lt;strong&gt;bounded&lt;/strong&gt; with a configurable size. When full, it &lt;strong&gt;back-pressures&lt;/strong&gt; &lt;strong&gt;Poller&lt;/strong&gt; so that it can follow up with the appropriate actions.&lt;/li&gt;
&lt;li&gt;The work queues are &lt;strong&gt;asynchronous&lt;/strong&gt;, which &lt;strong&gt;decouples&lt;/strong&gt; polling and message processing, allowing them to happen independently. This is in contrast to the &lt;strong&gt;poll-then-process&lt;/strong&gt; loop, where they are two sequential steps within a loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Poller
&lt;/h3&gt;

&lt;p&gt;In short, &lt;strong&gt;Poller&lt;/strong&gt; encapsulates everything related to &lt;code&gt;poll&lt;/code&gt; in Kafka:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It watches out for &lt;strong&gt;rebalance&lt;/strong&gt; events - e.g by registering a &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.html"&gt;ConsumerRebalanceListener&lt;/a&gt; - and coordinates other units to handle them.

&lt;ul&gt;
&lt;li&gt;For each newly &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.html#onPartitionsAssigned(java.util.Collection)"&gt;assigned&lt;/a&gt; &lt;code&gt;TopicPartition&lt;/code&gt;, it set up a new &lt;strong&gt;work queue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For each &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.html#onPartitionsRevoked(java.util.Collection)"&gt;revoked&lt;/a&gt; (or &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/ConsumerRebalanceListener.html#onPartitionsLost(java.util.Collection)"&gt;lost&lt;/a&gt;) &lt;code&gt;TopicPartition&lt;/code&gt;, it commands both &lt;strong&gt;Executor&lt;/strong&gt; and &lt;strong&gt;Offset Manager&lt;/strong&gt; to &lt;strong&gt;wrap up&lt;/strong&gt; related works and tears down the &lt;strong&gt;corresponding work queue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;It &lt;code&gt;poll&lt;/code&gt;s Kafka periodically using a &lt;strong&gt;short&lt;/strong&gt; (e.g 50ms), &lt;strong&gt;configurable interval&lt;/strong&gt;.  Since this is many times lower than the default &lt;code&gt;max.poll.interval.ms&lt;/code&gt;, while also not affected by message processing, we avoid the "rebalance storm" that plagues the &lt;strong&gt;poll-then-process&lt;/strong&gt; loop. Kafka won't mistaken our consumer as dead for not &lt;code&gt;poll&lt;/code&gt;ing often enough. In addition, we would know sooner if another &lt;strong&gt;rebalance&lt;/strong&gt; is going to happen.&lt;/li&gt;
&lt;li&gt;When we &lt;code&gt;poll&lt;/code&gt; more often, we can also use a lower &lt;code&gt;max.poll.interval.ms&lt;/code&gt; to speed up the &lt;strong&gt;rebalance&lt;/strong&gt; process.&lt;/li&gt;
&lt;li&gt;For each &lt;code&gt;TopicPartition&lt;/code&gt; that the &lt;strong&gt;Executor&lt;/strong&gt; cannot keep up with the rate of incoming messages, its corresponding work queue will become full and &lt;strong&gt;back-pressure&lt;/strong&gt; the &lt;strong&gt;Poller&lt;/strong&gt;. The &lt;strong&gt;Poller&lt;/strong&gt; would need to selectively &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#pause(java.util.Collection)"&gt;pause&lt;/a&gt; this &lt;code&gt;TopicPartition&lt;/code&gt;, so that subsequent &lt;code&gt;poll&lt;/code&gt;s &lt;strong&gt;won't pull in&lt;/strong&gt; more messages from it. When the queue is freed up again, it would &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#resume(java.util.Collection)"&gt;resume&lt;/a&gt; the same &lt;code&gt;TopicPartition&lt;/code&gt; to get new messages starting from the next &lt;code&gt;poll&lt;/code&gt;. That's why we can keep &lt;code&gt;poll&lt;/code&gt;ing. That's also why we use a short interval, so that we can "resume" faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;pause&lt;/strong&gt;(Collection&amp;lt;&lt;strong&gt;TopicPartition&lt;/strong&gt;&amp;gt; partitions)&lt;/p&gt;

&lt;p&gt;Suspend fetching from the requested partitions. Future calls to &lt;strong&gt;poll&lt;/strong&gt;(Duration) will not return any records from these partitions until they have been resumed using &lt;strong&gt;resume&lt;/strong&gt;(Collection).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Executor
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Executor&lt;/strong&gt; acts like a &lt;a href="https://en.wikipedia.org/wiki/Thread_pool"&gt;thread pool&lt;/a&gt;, where it maintains multiple workers to process the messages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executor&lt;/strong&gt; and the number of workers is tunable to optimize for different workloads e.g CPU bound, I/O bound, etc.&lt;/li&gt;
&lt;li&gt;Each &lt;strong&gt;work queue&lt;/strong&gt; is processed by one worker.&lt;/li&gt;
&lt;li&gt;One worker can be responsible for multiple &lt;strong&gt;work queue&lt;/strong&gt;s.&lt;/li&gt;
&lt;li&gt;For each &lt;strong&gt;work queue&lt;/strong&gt;, the worker processes its messages one by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this setup, messages within one partition is processed &lt;strong&gt;in order&lt;/strong&gt;, while messages from different partitions are processed &lt;strong&gt;in parallel&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offset Manager
&lt;/h3&gt;

&lt;p&gt;Each message in Kafka is associated with an &lt;strong&gt;offset&lt;/strong&gt; - an integer number denoting its position in the current partition. By storing this number, we essentially provide a checkpoint for our consumer. If it fails and comes back, it knows from where to continue. As such, it is vital for implementing various processing guarantees in Kafka:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;em&gt;at-most-once&lt;/em&gt;, we need to save &lt;code&gt;$offset + 1&lt;/code&gt; before processing &lt;code&gt;$offset&lt;/code&gt;. If our consumer fails before successfully process &lt;code&gt;$offset&lt;/code&gt; and restarts, it will continue from &lt;code&gt;$offset + 1&lt;/code&gt; and not reprocess &lt;code&gt;$offset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;at-least-once&lt;/em&gt;, we need to successfully process &lt;code&gt;$offset&lt;/code&gt; before saving &lt;code&gt;$offset + 1&lt;/code&gt;. If our consumer fails before saving &lt;code&gt;$offset + 1&lt;/code&gt; and restarts, it will continue from and reprocess &lt;code&gt;$offset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;exactly-once&lt;/em&gt; using an external &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#rebalancecallback"&gt;transactional storage&lt;/a&gt; - we need to process &lt;code&gt;$offset&lt;/code&gt; and save &lt;code&gt;$offset + 1&lt;/code&gt; within one transaction and roll back if anything goes wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in mind, we have &lt;strong&gt;Offset Manager&lt;/strong&gt; providing a consistent interface for other components to work with these saved offsets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If we store offsets within Kafka, it is responsible for &lt;strong&gt;manually&lt;/strong&gt; &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#commitSync()"&gt;committing offsets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If we decide to manage offsets using an external storage, it is responsible for retrieving from and saving into that storage.&lt;/li&gt;
&lt;li&gt;It allows &lt;strong&gt;Poller&lt;/strong&gt; and &lt;strong&gt;Executor&lt;/strong&gt; to save offsets either &lt;strong&gt;synchronously&lt;/strong&gt; or &lt;strong&gt;asynchronously&lt;/strong&gt; - in &lt;a href="https://en.wikipedia.org/wiki/Fire-and-forget"&gt;fire and forget&lt;/a&gt; fashion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offset Manager&lt;/strong&gt; storing behavior can be configured: in &lt;strong&gt;batched&lt;/strong&gt;, &lt;strong&gt;recurring&lt;/strong&gt; with a timer, etc...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What about Kafka's &lt;a href="https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_enable.auto.commit"&gt;auto commit&lt;/a&gt;? Confluent &lt;a href="https://docs.confluent.io/platform/current/clients/consumer.html#offset-management"&gt;claims&lt;/a&gt; that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using auto-commit gives you “at least once” delivery: Kafka guarantees that no messages will be missed, but duplicates are possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is true for delivery, however, it doesn't provide any guarantee for processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not &lt;em&gt;at-most-once&lt;/em&gt;: If some messages are successfully processed, and our consumer crashes before the next auto commit event, these messages are reprocessed.&lt;/li&gt;
&lt;li&gt;It's not &lt;em&gt;at-least-once&lt;/em&gt;: If auto commit kicks in, and our consumer crashes right afterward, some messages are lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Due to this, we always set &lt;code&gt;enable.auto.commit&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; and have &lt;strong&gt;Offset Manager&lt;/strong&gt; manage offsets manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Achieving processing guarantees
&lt;/h2&gt;

&lt;p&gt;Let's go through a few example use cases to see how the components work together to satisfy different processing guarantees.&lt;/p&gt;

&lt;h3&gt;
  
  
  At-most-once, offsets managed by Kafka
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k-UcUyq3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qg6aoemcdxqr8t5azi14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k-UcUyq3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qg6aoemcdxqr8t5azi14.png" alt="At most once"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;For &lt;em&gt;at-most-once&lt;/em&gt;, we just need to commit offsets &lt;strong&gt;before&lt;/strong&gt; processing the messages. We can do it right before processing each message. However, it doesn't give us a stronger guarantee while introducing more costs. Therefore, &lt;strong&gt;Poller&lt;/strong&gt; is responsible for it. After each &lt;code&gt;poll&lt;/code&gt;, it will tell &lt;strong&gt;Offset Manager&lt;/strong&gt; to save these offsets and &lt;strong&gt;wait&lt;/strong&gt; for a success acknowledgement from Kafka before queuing the messages for processing.&lt;/p&gt;

&lt;p&gt;Prior to a &lt;strong&gt;rebalance&lt;/strong&gt; event, it just needs to send a &lt;em&gt;fire-and-forget&lt;/em&gt; signal to &lt;strong&gt;Executor&lt;/strong&gt; to stop processing. It then takes down the work queues and gets back to wait for &lt;strong&gt;rebalance&lt;/strong&gt;. The lost messages are those still sitting in the queues or in the middle of processing. If we want to optimize for fewer lost without affecting the duration of &lt;strong&gt;rebalance&lt;/strong&gt;, we can use a smaller queue size.&lt;/p&gt;

&lt;h3&gt;
  
  
  At-least-once, offsets managed by Kafka
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HktTDLj7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/99yh6son86s0ye94ljd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HktTDLj7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/99yh6son86s0ye94ljd9.png" alt="At least once"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;at-least-once&lt;/em&gt;, we just need to make sure offsets are only saved &lt;strong&gt;after&lt;/strong&gt; the messages have been processed successfully. Consequently, if we were to process 10 messages, we wouldn't need to save offsets for all of them but only the last one.&lt;/p&gt;

&lt;p&gt;In this setup, &lt;strong&gt;Executor&lt;/strong&gt; will emit signals to &lt;strong&gt;Offset Manager&lt;/strong&gt; each time it completes processing for a message. &lt;strong&gt;Offset Manager&lt;/strong&gt; keeps track of the latest offset for each partition - which in total is not that many - and decide &lt;strong&gt;when&lt;/strong&gt; to commit them to Kafka. For example, we can set &lt;strong&gt;Offset Manager&lt;/strong&gt; to commit once every 5 seconds. This happens regardless of whether new messages are coming or not. &lt;em&gt;(Interestingly, this is important for older Kafka if we don't want to lose committed offsets in a low activity Kafka topic, see &lt;a href="https://issues.apache.org/jira/browse/KAFKA-4682"&gt;KAFKA-4682&lt;/a&gt;)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Prior to a &lt;strong&gt;rebalance&lt;/strong&gt; event, &lt;strong&gt;Poller&lt;/strong&gt; set a hard &lt;strong&gt;deadline&lt;/strong&gt; and notifies &lt;strong&gt;Executor&lt;/strong&gt; to wrap up its &lt;strong&gt;in-flight&lt;/strong&gt; processing and &lt;strong&gt;Offset Manager&lt;/strong&gt; to follow up with a &lt;strong&gt;last&lt;/strong&gt; commit. If the deadline has passed, or &lt;strong&gt;Poller&lt;/strong&gt; has received responses from others, it takes down the &lt;strong&gt;work queues&lt;/strong&gt; and gets back to wait for &lt;strong&gt;rebalance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To optimize for fewer duplicated processing, we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;looser&lt;/strong&gt; deadline, allowing more time for "wrapping up". However, it also increases timing for &lt;strong&gt;rebalance&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Offset Manager&lt;/strong&gt; to commit more often.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exactly-once, offsets managed externally
&lt;/h3&gt;

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

&lt;p&gt;In this case, offset saving and message processing needs to happen within one transaction. This means &lt;strong&gt;Executor&lt;/strong&gt; and &lt;strong&gt;Offset Manager&lt;/strong&gt; working closely together using &lt;strong&gt;synchronous&lt;/strong&gt; calls to make it happen.&lt;/p&gt;

&lt;p&gt;Following a &lt;strong&gt;rebalance&lt;/strong&gt; event, &lt;strong&gt;Poller&lt;/strong&gt; asks &lt;strong&gt;Offset Manager&lt;/strong&gt; for the saved offsets of current assignments. It then &lt;a href="https://kafka.apache.org/28/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#seek(org.apache.kafka.common.TopicPartition,long)"&gt;seek&lt;/a&gt;s to restore the saved positions before resuming to &lt;code&gt;poll&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;public void &lt;strong&gt;seek&lt;/strong&gt;(TopicPartition partition, long offset)&lt;/p&gt;

&lt;p&gt;Overrides the fetch offsets that the consumer will use on the next poll(timeout).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prior to a &lt;strong&gt;rebalance&lt;/strong&gt; event, &lt;strong&gt;Poller&lt;/strong&gt; notifies &lt;strong&gt;Executor&lt;/strong&gt; and waits for its response. &lt;strong&gt;Executor&lt;/strong&gt; rolls back its &lt;strong&gt;in-flight&lt;/strong&gt; transactions and return to &lt;strong&gt;Poller&lt;/strong&gt;. &lt;strong&gt;Poller&lt;/strong&gt; then takes down the work queues and gets back to wait for &lt;strong&gt;rebalance&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;We analyze the various issues with the &lt;strong&gt;loop-then-process&lt;/strong&gt; loop and come up with a more proper model for understanding and implementing Kafka Consumer. The downside is that it is much more complicated, and probably not easy for beginners. We blame this complexity on Kafka and its low level API.&lt;/p&gt;

&lt;p&gt;In practice, we probably won't do it ourselves but use a ready-made library that may or may not base on similar models: &lt;a href="https://doc.akka.io/docs/alpakka-kafka/current/home.html"&gt;Alpakka Kafka&lt;/a&gt;, &lt;a href="https://spring.io/projects/spring-kafka"&gt;Spring for Kafka&lt;/a&gt;, &lt;a href="https://github.com/zio/zio-kafka"&gt;zio-kafka&lt;/a&gt;, etc... Even then, the proposed model can be useful for evaluating these solutions or implementing new ones.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>tutorial</category>
      <category>concurrency</category>
      <category>parallelism</category>
    </item>
    <item>
      <title>Zero Downtime Reindex in Elasticsearch</title>
      <dc:creator>Linh Nguyen</dc:creator>
      <pubDate>Sun, 22 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/tuleism/zero-downtime-reindex-in-elasticsearch-13o8</link>
      <guid>https://dev.to/tuleism/zero-downtime-reindex-in-elasticsearch-13o8</guid>
      <description>&lt;h2&gt;
  
  
  Why reindexing data in Elasticsearch?
&lt;/h2&gt;

&lt;p&gt;Reindexing data is a common operation in working with Elasticsearch. When do we need to do it? Here are a few examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mapping change&lt;/strong&gt;: when we change how data is analyzed and indexed. For example: changing a field's analyzer, changing an analyzer's definition, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster upgrade or migration&lt;/strong&gt;: Elasticsearch can only read indices created in the previous major version &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html" rel="noopener noreferrer"&gt;since 5.x&lt;/a&gt;. So if we want to upgrade our Elasticsearch that is several major versions behind, we have to resort to reindexing. For example, Elasticsearch support &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade-remote.html" rel="noopener noreferrer"&gt;reindexing from a remote cluster&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prior arts
&lt;/h2&gt;

&lt;p&gt;We take a look at some available solutions to implement reindexing and analyze their pros and cons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Elasticsearch's &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html" rel="noopener noreferrer"&gt;official Reindex API&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy: not much work to do since it's readily available.&lt;/li&gt;
&lt;li&gt;Fastest solution.&lt;/li&gt;
&lt;li&gt;Work for both: reindex within the same cluster or reindex from a remote cluster.&lt;/li&gt;
&lt;li&gt;Works well if we can stop indexing data for a while (to wait for the reindex process to finish).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Hard to reason about consistency if writes still come to the old index during the reindex process.&lt;/li&gt;
&lt;li&gt;Meant for indices that must be "paused", so not really zero downtime.&lt;/li&gt;
&lt;li&gt;Only work with data that are already in Elasticsearch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Index Alias Wizardry
&lt;/h3&gt;

&lt;p&gt;In Elasticsearch, alias works as a &lt;strong&gt;pointer&lt;/strong&gt; to one or multiple indices. &lt;a href="https://www.elastic.co/blog/changing-mapping-with-zero-downtime" rel="noopener noreferrer"&gt;Many&lt;/a&gt; &lt;a href="https://blog.codecentric.de/en/2014/09/elasticsearch-zero-downtime-reindexing-problems-solutions/" rel="noopener noreferrer"&gt;have&lt;/a&gt; &lt;a href="https://medium.com/craftsmenltd/rebuild-elasticsearch-index-without-downtime-168363829ea4" rel="noopener noreferrer"&gt;written&lt;/a&gt; about using it for zero downtime reindexing: using one alias or using two, one for read and one for write, etc.&lt;/p&gt;

&lt;p&gt;Basically it works like this:&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%2Fnmceciikx91fp50c3217.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%2Fnmceciikx91fp50c3217.png" alt="Zero Downtime Reindexing using alias"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applications don't query the index directly, but through an alias &lt;code&gt;index_alias&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The alias points to the &lt;strong&gt;current&lt;/strong&gt; index &lt;code&gt;current_index&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When we want to reindex data, we create a new index &lt;code&gt;new_index&lt;/code&gt;, probably with a new mapping.&lt;/li&gt;
&lt;li&gt;Then we duplicate write: we write to both &lt;code&gt;current_index&lt;/code&gt; and &lt;code&gt;new_index&lt;/code&gt; at the same time.&lt;/li&gt;
&lt;li&gt;Once &lt;code&gt;new_index&lt;/code&gt; has caught up with &lt;code&gt;current_index&lt;/code&gt;, we update the alias to point to &lt;code&gt;new_index&lt;/code&gt; instead of &lt;code&gt;current_index&lt;/code&gt;. This can be done &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html" rel="noopener noreferrer"&gt;atomically&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If anything goes wrong, we just have to revert the final alias change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Straightforward.&lt;/li&gt;
&lt;li&gt;Revertible.&lt;/li&gt;
&lt;li&gt;Can include new fields from the source of truth outside of Elasticsearch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Only work within one Elasticsearch cluster.&lt;/li&gt;
&lt;li&gt;Requires quite some work, but handles only one use case.&lt;/li&gt;
&lt;li&gt;Most solutions don't mention how to keep data consistent between &lt;code&gt;current_index&lt;/code&gt; and &lt;code&gt;new_index&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A generic solution
&lt;/h2&gt;

&lt;p&gt;Above solutions are &lt;strong&gt;purely about data&lt;/strong&gt; coming into Elasticsearch. However, not only data, but the &lt;strong&gt;applications&lt;/strong&gt; that read and write these data are also important aspects to consider when we want to rollout a reindex operation.&lt;/p&gt;

&lt;p&gt;For example, when we do a major mapping change, we might need to update application code to be able to query from the new index.&lt;/p&gt;

&lt;p&gt;For upgrading Elasticsearch server version, we usually have to update the application code since the API &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes.html" rel="noopener noreferrer"&gt;might have changed&lt;/a&gt;. If we were to use a library like &lt;a href="https://github.com/sksamuel/elastic4s" rel="noopener noreferrer"&gt;elastic4s&lt;/a&gt;, upgrading server means upgrading client library &lt;a href="https://github.com/sksamuel/elastic4s#release" rel="noopener noreferrer"&gt;at the same time&lt;/a&gt;. To guarantee zero downtime, we need to be able to release application code and the new cluster version independently. How can we achieve that if we don't care about application code?&lt;/p&gt;

&lt;p&gt;By incorporating these considerations, we can achieve a generic reindexing solution that works for all mentioned use cases, while having no downtime.&lt;/p&gt;

&lt;p&gt;If we call this a recipe for reindexing, then here are the required ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;data source&lt;/strong&gt; that supports &lt;strong&gt;deterministic replay&lt;/strong&gt; and &lt;strong&gt;multiple concurrent readers&lt;/strong&gt;. Example: a &lt;a href="https://www.confluent.io/blog/apache-kafka-intro-how-kafka-works/#kafka-topics" rel="noopener noreferrer"&gt;Kafka topic&lt;/a&gt;, a &lt;a href="https://debezium.io/" rel="noopener noreferrer"&gt;change data capture&lt;/a&gt; stream, etc. Why? Because it allows us to have multiple independent writers/indexers that write/index the same set of data into different Elasticsearch indices in possibly different clusters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt; between &lt;strong&gt;readers&lt;/strong&gt; (applications that query from Elasticsearch) and &lt;strong&gt;writers&lt;/strong&gt; (applications that write/index into Elasticsearch). Why? Because it allows us to &lt;strong&gt;separate their deployment&lt;/strong&gt; (think microservices' &lt;a href="https://microservices.io/patterns/microservices.html" rel="noopener noreferrer"&gt;independently deployable&lt;/a&gt;). As a result, we can &lt;strong&gt;scale&lt;/strong&gt; and &lt;strong&gt;upgrade&lt;/strong&gt; them independently. We will also see later that it allows us to easily &lt;strong&gt;upgrade Elasticsearch code&lt;/strong&gt; from any arbitrary version.&lt;/li&gt;
&lt;li&gt;Writers that guarantee &lt;a href="https://microservices.io/patterns/communication-style/idempotent-consumer.html" rel="noopener noreferrer"&gt;idempotent processing&lt;/a&gt; under at-least-once delivery. Why? Because combining this with the data source above, we can &lt;strong&gt;guarantee data consistency&lt;/strong&gt; - all old and new indices will eventually have the same data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how the most basic architecture looks like in normal operation:&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%2F16hobftxzi8hqe8wylqw.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%2F16hobftxzi8hqe8wylqw.png" alt="Zero Downtime Reindexing Recipe"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have one writer/indexer &lt;code&gt;current_writer&lt;/code&gt; that writes/indexes into &lt;code&gt;current_index&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A separate reader &lt;code&gt;current_reader&lt;/code&gt; query from that index and forward the results to the clients. Alternatively, the reader can be a shared library embedded in the client applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see how it evolves when we want to reindex data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example #1: reindex into the same cluster e.g when mapping changes
&lt;/h3&gt;

&lt;p&gt;First, we create a new index &lt;code&gt;new_index&lt;/code&gt; with the updated mapping. Then we deploy a new writer &lt;code&gt;new_writer&lt;/code&gt; (either the same code as &lt;code&gt;current_writer&lt;/code&gt; or newer code if needed) that writes data into this index.&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%2Fg5h6uuri3zrtihatcyd1.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%2Fg5h6uuri3zrtihatcyd1.png" alt="Example #1 Step 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;new_index&lt;/code&gt; has caught up with &lt;code&gt;current_index&lt;/code&gt;, we redirect query requests from &lt;code&gt;current_reader&lt;/code&gt; to &lt;code&gt;new_index&lt;/code&gt;. This can be a configuration change on &lt;code&gt;current_reader&lt;/code&gt; or simply an alias update on Elasticsearch.&lt;/p&gt;

&lt;p&gt;If anything goes wrong, we just need to revert the final redirect change since &lt;code&gt;current_index&lt;/code&gt; is still up to date.&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%2F54e2fxguaep08pnxov2s.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%2F54e2fxguaep08pnxov2s.png" alt="Example #1 Step 2 Case 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For complex mapping changes, we might also need to update the reader code. In these cases, we can release a new version of reader - &lt;code&gt;new_reader&lt;/code&gt; that queries directly from &lt;code&gt;new_index&lt;/code&gt;. Once everything looks good, we redirect requests to &lt;code&gt;new_reader&lt;/code&gt; instead of &lt;code&gt;current_reader&lt;/code&gt;. Again, we just need to revert the last step if anything goes wrong.&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%2Fzwvao7liortlfxd0h48j.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%2Fzwvao7liortlfxd0h48j.png" alt="Example #1 Step 2 Case 2"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example #2: reindex into another cluster e.g when upgrading or doing migration
&lt;/h3&gt;

&lt;p&gt;First, we create a new index &lt;code&gt;new_index&lt;/code&gt; in the new cluster. Then we deploy a new writer &lt;code&gt;new_writer&lt;/code&gt; that writes data into this index. &lt;code&gt;new_writer&lt;/code&gt;'s code has been updated with newer Elasticsearch client library to talk to the new cluster.&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%2Frqkdcu6vr36kcb42twid.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%2Frqkdcu6vr36kcb42twid.png" alt="Example #2 Step 1"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Next, similar to example #1, we release a new reader &lt;code&gt;new_reader&lt;/code&gt; that query from &lt;code&gt;new_index&lt;/code&gt;. &lt;code&gt;new_reader&lt;/code&gt;'s code has been updated with newer Elasticsearch client library. If things looks good, we switch clients to use &lt;code&gt;new_reader&lt;/code&gt;. Again, this is fully revertible.&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%2F6qn35ibcknyn1bzpc3uf.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%2F6qn35ibcknyn1bzpc3uf.png" alt="recipe-example2-step2"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Final steps
&lt;/h3&gt;

&lt;p&gt;Once we're happy with the result, we just need to retire those &lt;strong&gt;"current"&lt;/strong&gt; indices, writers and readers. The diagram now looks exactly like the original.&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%2Fv5ulpol6gc0p3jum27bt.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%2Fv5ulpol6gc0p3jum27bt.png" alt="Recipe Final Steps"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison to other solutions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Not more complicated than using Index Alias.&lt;/li&gt;
&lt;li&gt;Generic solution that works for all use cases.&lt;/li&gt;
&lt;li&gt;All steps are incremental and revertible.&lt;/li&gt;
&lt;li&gt;Zero downtime. There is a clear plan for rolling back.&lt;/li&gt;
&lt;li&gt;Guarantee &lt;a href="https://en.wikipedia.org/wiki/Eventual_consistency" rel="noopener noreferrer"&gt;eventual consistency&lt;/a&gt; because of the requirements on the data source and writers.&lt;/li&gt;
&lt;li&gt;Freedom in deciding when the switch/redirect happens. If implemented properly, data is kept "in sync" in near real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Not the most performance method. However, it's very flexible. For example, we can scale writers up if we want to speed up reindexing.&lt;/li&gt;
&lt;li&gt;It requires organizational maturity in CI/CD/Ops to automate the process successfully. With that said, manually doing it is also not that hard since the steps are all laid out.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elasticsearch</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Smart Constructors in Scala</title>
      <dc:creator>Linh Nguyen</dc:creator>
      <pubDate>Sun, 16 Aug 2020 10:10:20 +0000</pubDate>
      <link>https://dev.to/tuleism/smart-constructors-in-scala-19a9</link>
      <guid>https://dev.to/tuleism/smart-constructors-in-scala-19a9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Sometimes, we need guarantees about the values in our program beyond what can be accomplished with the usual type system checks. Take for example an &lt;code&gt;Email&lt;/code&gt; type, which contains an email address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The problem with &lt;code&gt;Email&lt;/code&gt; is that &lt;code&gt;address&lt;/code&gt; can contain &lt;strong&gt;any&lt;/strong&gt; value of type &lt;code&gt;String&lt;/code&gt;, even if they are not valid email addresses. Due to that, code that uses &lt;code&gt;Email&lt;/code&gt; cannot assume that it's always a valid email address. Thus, we want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Constrain the set of possible value for &lt;code&gt;address&lt;/code&gt; to be the set of valid email addresses.&lt;/li&gt;
&lt;li&gt;Make sure every instance of &lt;code&gt;Email&lt;/code&gt; satisfies this constraint. In other words, every &lt;code&gt;Email&lt;/code&gt; instance contains a valid email address.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Smart Constructors is one solution for this: instead of normal constructors, we &lt;strong&gt;force&lt;/strong&gt; construction through "smart" functions that only return &lt;code&gt;Email&lt;/code&gt; instances when the input passes validation.&lt;/p&gt;

&lt;p&gt;In our case, we can use a function that returns &lt;code&gt;Option[Email]&lt;/code&gt;, &lt;code&gt;Either[Error, Email]&lt;/code&gt;, etc depends on the validation result. For demonstration, I use a very simple regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
  &lt;span class="nv"&gt;NaiveEmailRegex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;findFirstIn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Currently, I'm aware of 3 ways to implement Smart Constructors in Scala.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Straightforward One: &lt;code&gt;sealed trait&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;trait&lt;/code&gt;, it is very straightforward to implement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;NaiveEmailRegex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;findFirstIn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;With &lt;code&gt;sealed&lt;/code&gt;, we disallow attempts to &lt;code&gt;extends Email&lt;/code&gt; from outside of this source file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Email.fromString&lt;/code&gt; is the only way to construct an &lt;code&gt;Email&lt;/code&gt; from outside of this source file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Confusing One: &lt;code&gt;final case class private&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When it comes to modeling data, especially those that are immutable, case classes is the method of choice for Scala. Compared to traits, case classes come with many &lt;a href="https://docs.scala-lang.org/overviews/scala-book/case-classes.html"&gt;useful features&lt;/a&gt; out of the box: pattern matching, equality comparison, copying, etc.&lt;/p&gt;

&lt;p&gt;On the other hand, some of these make it harder to implement smart constructors. The implementations also differ depends on which Scala version (and compiler flags) you are using, causing confusion for beginners.&lt;/p&gt;

&lt;p&gt;Let's begin with an initial implementation in &lt;strong&gt;Scala 2.11&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="nf"&gt;private&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;With &lt;code&gt;final&lt;/code&gt;, we disallow attempts to &lt;code&gt;extends Email&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;private&lt;/code&gt;, construction through &lt;code&gt;new Email(value)&lt;/code&gt; can only takes place within the current source file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it is still possible to create an invalid &lt;code&gt;Email&lt;/code&gt; instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By using &lt;code&gt;copy(value = "badValue")&lt;/code&gt; to create a shallow copy of an instance of &lt;code&gt;Email&lt;/code&gt; with a bad value.&lt;/li&gt;
&lt;li&gt;By using &lt;code&gt;Email(value = "badValue")&lt;/code&gt;, which translates to calling &lt;code&gt;apply()&lt;/code&gt; on the &lt;code&gt;Email&lt;/code&gt;'s companion object to construct an &lt;code&gt;Email&lt;/code&gt; with a bad value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To fix these problems, we need to &lt;strong&gt;either hide or override&lt;/strong&gt; the &lt;code&gt;copy()&lt;/code&gt; and &lt;code&gt;apply()&lt;/code&gt; functions. For example, with &lt;code&gt;apply()&lt;/code&gt;, there are 2 choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Override it with &lt;code&gt;private&lt;/code&gt; modifier to hide the &lt;code&gt;apply()&lt;/code&gt; function, disallowing construction through &lt;code&gt;Email(value)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Override it with an alternative return type and implementation: &lt;code&gt;Email(value)&lt;/code&gt; is possible, but let's say, return &lt;code&gt;Option[Email]&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.11.x and 2.12.x
&lt;/h3&gt;

&lt;p&gt;It turns out it is not possible to override &lt;code&gt;apply()&lt;/code&gt; in &lt;strong&gt;vanilla&lt;/strong&gt; Scala 2.11.x. See this &lt;a href="https://stackoverflow.com/questions/19462598/scala-case-class-implementation-of-smart-constructors"&gt;StackOverflow question&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;To do that, we need to add the compiler flag &lt;code&gt;-Xsource:2.12&lt;/code&gt;, which is only available &lt;a href="https://github.com/scala/scala/releases/tag/v2.11.11"&gt;after 2.11.11&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Allow custom apply and unapply methods in case class companions. Also allows case class constructors and apply methods to be private. (In 2.11.11, -Xsource:2.12 is needed to enable these changes. In Scala 2.12.2, they are on by default.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this, our code becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="nf"&gt;private&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;???&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is the same for 2.12.x without any compiler flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.13.x and Dotty
&lt;/h3&gt;

&lt;p&gt;With &lt;strong&gt;vanilla&lt;/strong&gt; 2.13.x, the code is the same as 2.12.x. However, &lt;a href="https://github.com/scala/scala/releases/tag/v2.13.2"&gt;from 2.13.2&lt;/a&gt;, we can reduce the boilerplate by enabling &lt;code&gt;-Xsource:3&lt;/code&gt;. From &lt;a href="https://github.com/scala/scala/pull/7702"&gt;scala/scala#7702&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Backport from dotty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a case class constructor is private or private[foo]: the synthesized copy and apply methods will have the same access modifier.&lt;/li&gt;
&lt;li&gt;If a case class constructor is protected or protected[foo]: the synthesized copy method will have the same access modifier. The synthesized apply method will remain public, because protected doesn't make sense in an object.
Obviously, if a user defines a custom copy or apply method, that one—including its access modifier—will take precedence.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;We end up with the original code that we begin with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="nf"&gt;private&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Phew! It only takes a few Scala major releases to get what we want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tricky One: &lt;code&gt;sealed abstract case class private&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We don't normally see &lt;code&gt;abstract&lt;/code&gt; being used on a case class. However, for smart constructors, it works wonderfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="nf"&gt;private&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;NaiveEmailRegex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;findFirstIn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{})&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;With &lt;code&gt;sealed&lt;/code&gt;, we disallow attempts to &lt;code&gt;extends Email&lt;/code&gt; from outside this source file.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;abstract&lt;/code&gt;, we disallow construction through &lt;code&gt;new Email()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;abstract&lt;/code&gt;, &lt;code&gt;copy()&lt;/code&gt; and &lt;code&gt;apply()&lt;/code&gt; are not automatically generated. We don't have to worry about them.&lt;/li&gt;
&lt;li&gt;It just work™! See &lt;a href="https://gist.github.com/tpolecat/a5cb0dc9adeacc93f846835ed21c92d2"&gt;the original gist&lt;/a&gt; for more details.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;We verify that our smart constructors implementation works by using a test suite to check that allowed code &lt;strong&gt;Compiles&lt;/strong&gt; and the rest &lt;strong&gt;DoesNotCompile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Below is the test suite for &lt;code&gt;sealed abstract case class private&lt;/code&gt;. The rest can be found in the &lt;a href="https://github.com/tuleism/scala-smart-constructors"&gt;code repository&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="s"&gt;"AbstractCaseClassExample"&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;AbstractCaseClassExample.Email&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fromString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exampleEmail&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt; &lt;span class="c1"&gt;// apply now return Option&lt;/span&gt;
  &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;value&lt;/span&gt; &lt;span class="n"&gt;mustBe&lt;/span&gt; &lt;span class="n"&gt;exampleEmail&lt;/span&gt; &lt;span class="c1"&gt;// access ok&lt;/span&gt;

  &lt;span class="c1"&gt;// case class's unapply()&lt;/span&gt;
  &lt;span class="nf"&gt;assertCompiles&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
      |email match { case Email(value) =&amp;gt; value }
      |"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stripMargin&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// companion's apply()&lt;/span&gt;
  &lt;span class="nf"&gt;assertDoesNotCompile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
      |Email(exampleEmail)
      |"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stripMargin&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// public constructor&lt;/span&gt;
  &lt;span class="nf"&gt;assertDoesNotCompile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
      |new Email(exampleEmail)
      |"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stripMargin&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// extends trait&lt;/span&gt;
  &lt;span class="nf"&gt;assertDoesNotCompile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
      |new Email {
      |  override def value: String = exampleEmail
      |}
      |"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stripMargin&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// case class's copy()&lt;/span&gt;
  &lt;span class="nf"&gt;assertDoesNotCompile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
      |email.copy(value = exampleEmail)
      |"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stripMargin&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;All of these allow us to implement smart constructors. My favorite one is &lt;code&gt;sealed abstract case class private&lt;/code&gt; because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It allows us to use &lt;code&gt;case class&lt;/code&gt; (instead of &lt;code&gt;trait&lt;/code&gt;, which is more verbose) and most of its &lt;a href="https://docs.scala-lang.org/overviews/scala-book/case-classes.html"&gt;goodness&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It looks the same in all Scala version. Cross-building is a breeze.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>scala</category>
      <category>programming</category>
      <category>tricks</category>
    </item>
  </channel>
</rss>
