<?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: prumand</title>
    <description>The latest articles on DEV Community by prumand (@prumand).</description>
    <link>https://dev.to/prumand</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%2F621184%2F1493f51b-8d0e-4070-8fcb-220a97628590.jpeg</url>
      <title>DEV Community: prumand</title>
      <link>https://dev.to/prumand</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prumand"/>
    <language>en</language>
    <item>
      <title>Opentelemetry for python</title>
      <dc:creator>prumand</dc:creator>
      <pubDate>Thu, 28 Sep 2023 15:05:40 +0000</pubDate>
      <link>https://dev.to/prumand/opentelemetry-for-python-535l</link>
      <guid>https://dev.to/prumand/opentelemetry-for-python-535l</guid>
      <description>&lt;p&gt;I have a cli-app (see &lt;a href="https://dev.to/prumand/lets-measure-1co5"&gt;my previous post&lt;/a&gt;) and I want to collect some usage stats. Of course &lt;a href="https://opentelemetry.io/"&gt;opentelemetry&lt;/a&gt; is the way to go and python support is pretty far.&lt;/p&gt;

&lt;p&gt;I’m using &lt;a href="https://www.jaegertracing.io/"&gt;jaeger&lt;/a&gt; for my traces. I don’t need live observability, but just some analytics from time to time. So I’d prefer starting a container and ingesting traces which are stored on disk on demand.&lt;/p&gt;

&lt;p&gt;Unfortunately there is no to-disk exporter for traces out of the box, but it’s easy to set up. First for writing to files, I’ll just use the standard python logging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logging.handlers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RotatingFileHandler&lt;/span&gt;


&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RotatingFileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxBytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backupCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'%(message)s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's ok to rotate logs from time to time. I can alway crank up the &lt;code&gt;backupCount&lt;/code&gt;, if I think it’s important.&lt;/p&gt;

&lt;p&gt;I’m using the standard otel-ConsoleExporter and change the formatter, since I want all traces to be written on one line. That makes it easier to ingest the file later. Further I change the output of the Exporter to my customer implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LogOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'makit.cli'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'makit.cli'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So basically I just need to add the traces to my action-functions and I’ve all I need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;get_app&lt;/span&gt;&lt;span class="p"&gt;()()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sidenote
&lt;/h2&gt;

&lt;p&gt;For me it’s fine to use span events as logging. Unfortunately those don’t show up in the jaeger-ui. But I tried something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OtelLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LogRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_current_span&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives me all the information I need and I thought about setting the span status based on the log-level.&lt;/p&gt;

</description>
      <category>otel</category>
      <category>python</category>
      <category>o11y</category>
      <category>devops</category>
    </item>
    <item>
      <title>Let's measure</title>
      <dc:creator>prumand</dc:creator>
      <pubDate>Tue, 26 Sep 2023 10:31:12 +0000</pubDate>
      <link>https://dev.to/prumand/lets-measure-1co5</link>
      <guid>https://dev.to/prumand/lets-measure-1co5</guid>
      <description>&lt;p&gt;I’m guilty of undermeasuring. With the next side project I want to change that, and see if I can do better. Basically I want to  use my abilities to program, to help my everyday routines. It’s mainly about integrating all the great tools already known and finding a different approach to use them.&lt;/p&gt;

&lt;p&gt;For now I want to leave out the details of the implementation, but to give you a glimpse of what the starting point looks like. I've already established a foundational setup that allows me to configure commands similar to the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;Greet a planet&lt;/span&gt;
    &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shell&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Hello ${planet}!"&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;planet&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The planet you want to greet.&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;autocomplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Earth&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Pluto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building something like this is really fun, but you can easily get off track. I think the best way to guardrail my efforts is to define what I want to achieve and measure the outcomes. It might be difficult to find, but I’m sure it’s possible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All important decision makers could benefit from learning that anything they really need to know is measurable. &lt;em&gt;[How to measure anything : finding the value of “intangibles” in business. Douglas W. Hubbard. – 2nd ed.]&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let’s find out what we want to measure.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Is Usage Success?&lt;br&gt;
I go with a definite maybe. First self deception is a thing, so an easy way to rate the commands used, will hopefully add quality as a dimension. Something &lt;code&gt;cli rate-last [1-5]&lt;/code&gt;, will help me capture my emotion in the moment, even if I only rate exceptional usage.&lt;br&gt;
Secondly, success is more than just usage. I want to track the projects I use my app with and evaluate the success of the project (failing and learning is fine) and the impact of the app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrating as focus&lt;br&gt;
Third party software and services are so integral to my work and a major part of the vision. That’s why I want to track it besides the general success. The app might have integrations backed in, but it’s not its main purpose. So all software and services which are triggered, used or configured with my app, should be tracked.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m aiming at the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reaching 200 calls per week of the app within the two months,&lt;/li&gt;
&lt;li&gt;no more than 20/100 bad calls,&lt;/li&gt;
&lt;li&gt;10 integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll follow-up on how I collect the data.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>From Domain to Tasks</title>
      <dc:creator>prumand</dc:creator>
      <pubDate>Mon, 18 Sep 2023 16:45:02 +0000</pubDate>
      <link>https://dev.to/prumand/from-domain-to-tasks-2lpa</link>
      <guid>https://dev.to/prumand/from-domain-to-tasks-2lpa</guid>
      <description>&lt;p&gt;Not long ago, I had a discussion with a colleague. I told him that if he can’t explain his planned work to his peers, he should probably work on his plan. Kind of paraphrasing  Richard Feymann:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you can't explain something to a first year student, then you haven't really understood it. &lt;br&gt;
[&lt;a href="https://en.wikiquote.org/wiki/Talk:Richard_Feynman"&gt;https://en.wikiquote.org/wiki/Talk:Richard_Feynman&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem seems easy enough, but in this context the problem was also finding what to do. Kind of a “Full Stack Engineer” for a whole slice of a domain. Maybe “Full Domain Engineer”?&lt;/p&gt;

&lt;p&gt;As a Full Domain Engineer getting your list of tasks is not merely structuring around ready made requirements, but finding those requirements in the first place. This needs a lot of communication. To put all that information into a form that can be understood and be worked with we require models = representations.&lt;/p&gt;

&lt;p&gt;Event Storming helps with some definitions and ways to put them together. (see &lt;a href="https://miro.com/miroverse/event-storming/"&gt;https://miro.com/miroverse/event-storming/&lt;/a&gt; for a nice walk-through). But the transition from a model to a list of tasks is pretty hard. Some problems I had were the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leaking technical decisions, especially past ones, into the model. For example some events which only occur because of a workaround.&lt;/li&gt;
&lt;li&gt;Document decisions on the semantics of the model. Even though I feel like a wiki page should do, that failed me on follow ups.&lt;/li&gt;
&lt;li&gt;Differentiating between the domain and technical things. That was especially hard, when my team was providing technical infrastructure. The domain is infrastructure, but the technical things are the infrastructure. So what is the domain?&lt;/li&gt;
&lt;li&gt;Our Brains need time to process. Don't transition into task mode right after a modelling session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last but not least, I think the whole modeling session is to inform, not to create tasks. Finally we need tasks, but I always felt they are created way too early.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-339435106961854464-214" src="https://platform.twitter.com/embed/Tweet.html?id=339435106961854464"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-339435106961854464-214');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=339435106961854464&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>ddd</category>
      <category>learning</category>
    </item>
    <item>
      <title>Let the Tests Design</title>
      <dc:creator>prumand</dc:creator>
      <pubDate>Thu, 14 Sep 2023 06:37:30 +0000</pubDate>
      <link>https://dev.to/prumand/let-the-tests-design-204d</link>
      <guid>https://dev.to/prumand/let-the-tests-design-204d</guid>
      <description>&lt;p&gt;I'm currently building myself a personal cli, which helps me integrate coded automation into my daily routines. After some experiments, I though that my next implementation could be here to stay. That's why I started with TDD and after some coding I ended up with the following test for the entrypoint of my typer-app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main.get_config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main.get_action_factory"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_create_action_with_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_action_factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_config_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Given
&lt;/span&gt;    &lt;span class="n"&gt;action_factory_mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ActionFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;create_action_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_factory_mock&lt;/span&gt;
    &lt;span class="n"&gt;config_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DirectoryPath&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;get_config_mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config_service&lt;/span&gt;

    &lt;span class="c1"&gt;# When
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"create-action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"new-action"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Then
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;create_action_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;action_factory_mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-action"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought it was a good to idea to let the shell entrypoint handle all the linking together, but there is a lot to mock. All this to answer whether the action_factory was called correctly.&lt;br&gt;
So it's time to let the tests drive the design. I decided to pull out the service creation into a service factory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7obViEwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q23w1ci8mdqh90n311jf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7obViEwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q23w1ci8mdqh90n311jf.png" alt="pulling out service container" width="721" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After four commits I get the following code change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    action_factory = get_action_factory(get_config().action_path)
&lt;/span&gt;&lt;span class="gi"&gt;+    action_factory = get_action_factory()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which allowed simpler test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main.get_action_factory"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_create_action_with_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_action_factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Given
&lt;/span&gt;    &lt;span class="n"&gt;action_factory_mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ActionFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;create_action_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_factory_mock&lt;/span&gt;

    &lt;span class="c1"&gt;# When
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"create-action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"new-action"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Then
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;action_factory_mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-action"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks way better. I'll follow up with some more coding done. Let's see what other decisions the tests designed.&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>python</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Save me from SAFe</title>
      <dc:creator>prumand</dc:creator>
      <pubDate>Wed, 28 Apr 2021 19:27:19 +0000</pubDate>
      <link>https://dev.to/prumand/save-me-from-safe-1h43</link>
      <guid>https://dev.to/prumand/save-me-from-safe-1h43</guid>
      <description>&lt;p&gt;My team introduced &lt;a href="https://www.scaledagileframework.com/"&gt;Scaled Agile Framework or SAFe&lt;/a&gt; and we already started the first iteration. A quick glance at their documentation reminded me of Martin Folwer’s “&lt;a href="https://martinfowler.com/articles/agile-aus-2018.html"&gt;Agile Industrial Complex&lt;/a&gt;”.&lt;br&gt;
I did some more research on what other people on think about it:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dcIiourk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1141253939/Mary-120a_normal.jpg" alt="Mary Poppendieck profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Mary Poppendieck
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @mpoppendieck
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      I am often asked what I think about scaled agile frameworks. This slide does a good job of summing up my viewpoint, except for the last point. We need more research on how successful large companies coordinate the work of small autonomous teams to achieve larger goals. &lt;a href="https://t.co/8Wa2Y8xBdH"&gt;twitter.com/TotherAlistair…&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      15:37 PM - 11 Dec 2019
    &lt;/div&gt;

      &lt;div class="ltag__twitter-tweet__quote"&gt;
        &lt;div class="ltag__twitter-tweet__quote__header"&gt;
          &lt;span class="ltag__twitter-tweet__quote__header__name"&gt;
            Alistair Cockburn
          &lt;/span&gt;
          @TotherAlistair
        &lt;/div&gt;
        I'm really liking the writing on this slide. I hadn't thought of it that way, good to see. https://t.co/TgkkBjY1fU
      &lt;/div&gt;

    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1204787228863533057" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1204787228863533057" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1204787228863533057" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--fGzel1td--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/796961836/bio-photo-square_normal.jpg" alt="Daniel Terhorst-North profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Daniel Terhorst-North
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @tastapod
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      “SAFe is just another word for nothing left to lose”
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      22:12 PM - 13 Oct 2018
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1051234257392345088" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1051234257392345088" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1051234257392345088" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;See also Daniel Terhorst-North’s &lt;a href="https://dannorth.net/2018/01/26/in-praise-of-swarming/"&gt;in praise of swarming&lt;/a&gt;.&lt;br&gt;
Some one even compiled a &lt;a href="https://www.smharter.com/blog/safe-a-collection-of-comments-from-leading-experts/"&gt;list of comments on SAFe&lt;/a&gt;. Which lead to me &lt;a href="https://www.youtube.com/embed/150OSyFUU_w?start=2117"&gt;video from Alistair Cockburn&lt;/a&gt;, which I sum up as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Yeah, standardize vocabulary 👍,&lt;/li&gt;
&lt;li&gt;but investing into collaboration and your ability to deliver could yield better results. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally I stumbled over the following quote from &lt;a href="https://www.thoughtworks.com/radar/techniques?blipid=793"&gt;ThoughtWork's Technology Radar Vol. 24 sums&lt;/a&gt;, which matches my feeling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We've come across organizations struggling with SAFe's over-standardized, phase-gated processes. Those processes create friction in the organizational structure and its operating model. It can also promote silos in the organization, preventing platforms from becoming real business capabilities enablers. The top-down control generates waste in the value stream and discourages engineering talent creativity, while limiting autonomy and experimentation in the teams&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So why did we introduce SAFe?
&lt;/h2&gt;

&lt;p&gt;We are a young team and we need strategic alignment with company goals. Direction and support is needed to transition our team to do the right things.&lt;/p&gt;

&lt;p&gt;But we are only one team, so we for sure miss scale. Some tools might provide good input, but introducing them all at once deprives us from learning. We will not understand what caused positive effects and will be overwhelmed by too many new techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  But why does it feel so wrong?
&lt;/h2&gt;

&lt;p&gt;For example I think, it's a good idea to have cycle and a prior week reserved for communication and planing. In my experience predictability is rated too high, but that's for a different article. Nevertheless predicting a long cycle (10 weeks) and forcing the team to predict 5 minor cycle (2 weeks) limits team autonomy, produces overhead and creates a constant environment of pressure. All this with little to gain, since the 10 weeks were already estimated. Maybe the team would have even chosen two weeks sprints.&lt;/p&gt;

&lt;p&gt;We can only dedicate about 50% (or even less) for work on features. We have a lot of bugs and operational work. We need to clean up. I don't think that you should separate cleaning up your architecture from your business work. Not separating those, leads to a more aligned cleaning up of technical debt and to culture where quality is built-in, rather than an afterthought.&lt;/p&gt;

&lt;p&gt;Introducing a Product Owner to take care of customer interactions and product vision, but leave the developers out of it, seems a bit strange to me. Especially since I experienced communicating to be the major part of software development. There is an ongoing discussion about the Product Owner role (see &lt;a href="https://www.infoq.com/news/2020/09/product-owner-good-bad-complex/"&gt;here&lt;/a&gt;), but &lt;a href="https://www.scaledagileframework.com/product-owner/"&gt;SAFe's definition&lt;/a&gt; seems to match the negative aspects.&lt;/p&gt;

&lt;h2&gt;
  
  
  So stay safe from SAFe!?
&lt;/h2&gt;

&lt;p&gt;I admit that I made up my mind early. Finding out that &lt;a href="https://www.scaledagile.com"&gt;https://www.scaledagile.com&lt;/a&gt; overwrites the HTML copy event to add a copy right disclaimer, increased my resentment. Maybe this should not be part of the evaluation, but it's a weak indicator that it doesn't fit my view of software development.&lt;/p&gt;

&lt;p&gt;I think the fundamentals (e.g. lean, agile, team topologies) used and quoted by SAFe are legit. Reading up on those helps a lot and all of them are aware of context. Having a standardized way to plump all tools together would be great, but we are just not there yet. &lt;/p&gt;

</description>
      <category>agile</category>
      <category>leadership</category>
      <category>engineering</category>
    </item>
  </channel>
</rss>
