<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title></title>
      <link>https://fpsd.codes</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://fpsd.codes/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sun, 31 Dec 2023 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Clojure Bites - Mazeboard 5 - More on actions, tests, schema and future plans</title>
          <pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;With the latest changes, described &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;,
we have come to a point were game and UI logic communicate with actions and
event, using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.async&quot;&gt;clojure.core.async&lt;&#x2F;a&gt; as an
abstraction of the communication channel, the approach can be briefly described as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;UI send actions to the game logic&lt;&#x2F;li&gt;
&lt;li&gt;Game logic handle the actions, emit the events and change its state&lt;&#x2F;li&gt;
&lt;li&gt;UI logic handle events, changing its state and view accordingly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For now, all of this is happening inside the same browser but given that we
have already in place and abstraction of the communication channel, it
should not be hard to put a network between the two layers.&lt;&#x2F;p&gt;
&lt;p&gt;Before moving to that topic, though, few more changes are needed. There is still quite
some hardcoded stuff, for example the game state, the user actions, the players
data just to name the more relevant ones, so first things to address will be:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;calculate the valid &lt;code&gt;:move&lt;&#x2F;code&gt; actions by looking at the player position in the board&lt;&#x2F;li&gt;
&lt;li&gt;simulate user actions instead of generating events based on hardcoded game state&lt;&#x2F;li&gt;
&lt;li&gt;start from a plain game state a build it with actions (starting with simulated ones, for now)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;At this point the base infrastructure will be more or less ready and I think it 
will be a good time to start adding some tests because, as the game logic will
grow, adding more actions and events, it will be easier to catch regressions or to
refactor some bits of the project (very likely to happen). The same applies to
the schema of actions and events, it would be nice to validate what goes back and
forth in the communication channels.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll close this update reasoning about a practical approach to create turn
based, multiplayer games. If you are curious jump &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;#multiplayer-architecture&quot;&gt;there&lt;&#x2F;a&gt;
directly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;calculating-valid-moves&quot;&gt;Calculating valid moves&lt;&#x2F;h1&gt;
&lt;p&gt;Up until now, after a user action the next one was hardcoded, if I want to make
a real game then the next possible actions must be calculated based on it is
actually possible, for example move to east only if the door to east is open.
This is based on where the player is positioned on the board.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;valid-move-actions
&lt;&#x2F;span&gt;&lt;span&gt;  [target board position]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; board [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:tiles&lt;&#x2F;span&gt;&lt;span&gt; position])
&lt;&#x2F;span&gt;&lt;span&gt;   tile&#x2F;open-walls
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mapv &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[direction] [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction&lt;&#x2F;span&gt;&lt;span&gt; direction &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target&lt;&#x2F;span&gt;&lt;span&gt; target}]))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case we calculate the valid &lt;code&gt;:move&lt;&#x2F;code&gt; actions based on the open walls of the
tile where the player is in. As we will add more actions we will have to do something
similar. The nice thing is that now it is possible to use both players when developing
the game locally!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;simulate-user-actions&quot;&gt;Simulate user actions&lt;&#x2F;h1&gt;
&lt;p&gt;Now that it is possible to generate the available actions based on what the 
player can actually do, it is time to move out the hardcoded events and 
only use player actions to update the game state, at this stage the actions that
we want to (or are able to) support are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;create game&lt;&#x2F;li&gt;
&lt;li&gt;join game&lt;&#x2F;li&gt;
&lt;li&gt;play action&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To simplify, the board layout will still be hardcoded, generating a random board
will be addressed in a later post.&lt;&#x2F;p&gt;
&lt;p&gt;We will change from a predefined game state and events to a plain state and
simulating user actions to create and start a game; it can seem like an unnecessary
step but I&#x27;d argue that it can help to identify how the players will interact
with the game, making the code closer to the final shape.&lt;&#x2F;p&gt;
&lt;p&gt;Given that after initializing the game we already have the actions channel, simulating
player actions is as simple as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;simulate-first-user-actions
&lt;&#x2F;span&gt;&lt;span&gt;  [actions-chan]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;go
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;gt;!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:create-game&lt;&#x2F;span&gt;&lt;span&gt;])
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;gt;!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join-game &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}])
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;lt;! &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;timeout &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;500&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;gt;!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join-game &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;two&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}])
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;lt;! &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;timeout &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;500&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&amp;gt;!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:start-game&lt;&#x2F;span&gt;&lt;span&gt;])))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;tests&quot;&gt;Tests&lt;&#x2F;h1&gt;
&lt;p&gt;At this point we have tested the new game layout manually, as we move forward,
adding more actions and events it will be harder to keep track of all code paths
and use case, so I think it is time to add some tests to help us move forward.
People familiar with the TDD would argue that tests would have helped earlier
because we can exercise the internal API sooner, discovering weaknesses (in 
its design or implementation) but at the same time I had no clear idea about how
the code would have evolved; what is the value of a well tested bad code design?
This is clearly personal taste, so use whatever work for you.&lt;&#x2F;p&gt;
&lt;p&gt;I am keeping most of the code in cljc files, because most of it can be used in 
different runtimes, even if at this time I am more interested in the JS side of it.
shadow-cljs has a basic test runner that is more than enough for my needs, I don&#x27;t
even need to address the browser at this point, at least for the game logic, and I
will leverage this opportunity by having a &lt;code&gt;nodejs&lt;&#x2F;code&gt; target test alias in the 
&lt;code&gt;shadow-clj.edn&lt;&#x2F;code&gt; project file, that look like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;edn&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-edn &quot;&gt;&lt;code class=&quot;language-edn&quot; data-lang=&quot;edn&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:builds &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;;; ... other build targets
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:tests
&lt;&#x2F;span&gt;&lt;span&gt;          {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target :node-test
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:output-to &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;test-out&#x2F;tests.js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:autorun true&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;          }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can run &lt;code&gt;npx shadow-cljs watch game tests&lt;&#x2F;code&gt; and have the tests to run each time
I save sources and tests, which is quite convenient! Shadow-Cljs has a lot of
configuration options, even for tests, so I&#x27;d suggest you to take a look at the
&lt;a href=&quot;https:&#x2F;&#x2F;shadow-cljs.github.io&#x2F;docs&#x2F;UsersGuide.html#_testing&quot;&gt;docs&lt;&#x2F;a&gt; to get familiar
with what it offers. It is worth mentioning that in this case I
am targeting nodejs as the runtime and not the browser, this is because the code that
I am testing is not related to the DOM but just the actions to events game loop; when
or if it will come the time to test the UI I&#x27;ll have to target the browser, which is
something I still have to look at but it is well supported by shadow-cljs, again
refer to the extensive documentation if you want to learn how it works.&lt;&#x2F;p&gt;
&lt;p&gt;Now the project is ready for the next step, that is to make it playable by multiple
players. &lt;&#x2F;p&gt;
&lt;h1 id=&quot;multiplayer-architecture&quot;&gt;Multiplayer on the server, or not?&lt;&#x2F;h1&gt;
&lt;p&gt;So far the game logic and UI are running in the same process, and communicating
via &lt;code&gt;core.async&#x2F;chan&lt;&#x2F;code&gt;, the following diagram shows how it works.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;local-architecture-2023-12-31-0933.png&quot; alt=&quot;local-dev-setup&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is convenient while developing but there is no way to play with other people!&lt;&#x2F;p&gt;
&lt;p&gt;A general approach for multiplayer games is to have a server somewhere which will
handle the game logic and updates all clients when something happens. Given the
current setup of the code this could be easily achieved by running the actions-&amp;gt;events
game loop in a web server which would receive actions via WebSocket and broadcast
events using the same WebSocket connection; another approach could be to have a 
endpoint where players can send actions (and possibly receive validation errors) and
broadcast events via SSE. My favorite setup would include:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;aleph&quot;&gt;Aleph&lt;&#x2F;a&gt; web server to handle calls to the actions endpoint and broadcast events via SSE&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;metosin&#x2F;reitit&quot;&gt;Reitit&lt;&#x2F;a&gt; to define the endpoints and handle the content negotiation&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;metosin&#x2F;malli&quot;&gt;Malli&lt;&#x2F;a&gt; to define and validate actions and events schema&lt;&#x2F;li&gt;
&lt;li&gt;Bonus, but not really needed at this point, some durable storage to store the state of the game; a relational DB plus &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;seancorfield&#x2F;honeysql&quot;&gt;HoneySQL&lt;&#x2F;a&gt; are battle tested solutions but lately I am having fun with datalog DBs. I will see which direction to take when it will come the time to work on this part.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here is a diagram of how the this client server setup may look like:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;client-server-architecture-2023-12-31-0933.png&quot; alt=&quot;client-server-diagram&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Another approach I am considering is inspired by how early multiplayer games worked
back in the past century, in the nineties (oh my, how old am I...).&lt;&#x2F;p&gt;
&lt;p&gt;In that setup one instance of the game works both as a client and a server, other 
players in the same network (or with a direct connection) connect their game client 
to the remote instance of the game which is responsible of updating the &amp;quot;official&amp;quot;
game state and sending updates to the clients. One nice property of this setup is 
that clients could reduce the latency of the network by pre-computing the next state
of the game using the same code which was running at the server side and possibly
adjust the state later if it diverged from what the server said it should have been.
This method is called client side prediction, I think it has been invented at
ID Software.&lt;&#x2F;p&gt;
&lt;p&gt;This worked quite well and did not require a dedicated server to play the game,
just the players&#x27; PCs and some networking gear. At that time I had few (cheap) network
cards that I could lend to friends plus the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;10BASE2&quot;&gt;cables&lt;&#x2F;a&gt;
to setup our LAN and play. Today, mostly everyone who can use a computing device can
access the internet in a way or another so the network part can be considered as
generally available.&lt;&#x2F;p&gt;
&lt;p&gt;Can we replicate this approach with browser based games? After all the game logic
can run in the browser and the UI layer can communicate with the game logic via a 
network abstraction, so it looks like that we can at least dream about not needing
a full featured server to run our multiplayer game, all we need is a way to let 
all the instances of the game to talk! What do have we today (end of 2023) to implement
this approach?&lt;&#x2F;p&gt;
&lt;p&gt;First option is &lt;a href=&quot;https:&#x2F;&#x2F;webrtc.org&quot;&gt;WebRTC&lt;&#x2F;a&gt; which gives us the ability to create
peer to peer connections between the players. It is available for the browsers in the
form of Javascript API and Android and iOS in the form of libraries; the only,
minor, downside is that to connect two peers it requires a server side service for
peer discovery and NAT &lt;a href=&quot;https:&#x2F;&#x2F;webrtc.org&#x2F;getting-started&#x2F;peer-connections&quot;&gt;traversing&lt;&#x2F;a&gt;.
This is an interesting option that I will explore in future.&lt;&#x2F;p&gt;
&lt;p&gt;The second option is to coordinate the communication between the players with a 
simple web app that can receive actions from a POST endpoint and broadcast events via
SSE. The benefit is simplicity (at least if you are already familiar with this tech)
and control which will come in handy later, for example to add authentication, 
persistent storage and so on.&lt;&#x2F;p&gt;
&lt;p&gt;To be honest, how the players will communicate is just an implementation detail and
it does not change how the architecture is designed:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;peer-to-peer-architecture-2023-12-31-0933.png&quot; alt=&quot;peer-to-peer-architecture&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The full &lt;a href=&quot;https:&#x2F;&#x2F;excalidraw.com&#x2F;&quot;&gt;Excalidraw&lt;&#x2F;a&gt; canvas is available &lt;a href=&quot;&#x2F;multiplayer-canvas.excalidraw&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;Now that the multiplayer architecture is more or less designed it is time to start
thinking about how to implement the thin communication layer. With a network layer
it will become more relevant to have a well described actions and events schema, for 
which I&#x27;ll use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;metosin&#x2F;malli&quot;&gt;malli&lt;&#x2F;a&gt;. The game logic must also
be completed to have a full playable game but this can leverage the local development
setup, without the complication of a network; fortunately both aspects can be developed
in parallel and are not blocking each other.&lt;&#x2F;p&gt;
&lt;p&gt;I am happy to close this year with all the foundation already laid out,
Happy New Year everyone!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstodon.org&#x2F;@fpsd&#x2F;111674732071536062&quot;&gt;Mastodon&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;18v5voh&#x2F;clojure_bites_last_post_of_the_year_with_usual&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;clojurians.slack.com&#x2F;archives&#x2F;C8NUSGWG6&#x2F;p1704022048184439&quot;&gt;Clojurians&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;x.com&#x2F;focaskater&#x2F;status&#x2F;1741418357327344099?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_intro-activity-7147209118502621184-qYSe?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Mazeboard 4 - From commands to events for a better separation of concerns</title>
          <pubDate>Tue, 19 Dec 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-4-commands-to-events/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-4-commands-to-events/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-4-commands-to-events/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;Last &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;post&lt;&#x2F;a&gt;
was about the separation of game and UI logic, where game and UI state have been
made independent by turning user actions to commands that would have updated
both game and UI state. That was possible because both state maps share the same
structure; even if convenient initially (you use the same code for both) it can 
be limiting in the long run, who knows how the next UI will be rendered tomorrow?
It is also a matter of separation of concerns, the current schema has data that is
not needed for the game logic, for example some element classes, and the same
applies to the UI layer.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;from-commands-to-events&quot;&gt;From commands to events&lt;&#x2F;h1&gt;
&lt;p&gt;Given that the final goal is to have independent state schema in the game and UI
logic the approach based on shared commands which will update both states would
not work anymore; the new approach instead will be based on events which will be
responsible for describing what happened and then each layer will handle them in a
way that best fits their needs (for example updating the view, triggering animations
etc in the UI layer). We will transition from a game loop like the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Game wait for actions&lt;&#x2F;li&gt;
&lt;li&gt;UI send the action after a user click&lt;&#x2F;li&gt;
&lt;li&gt;Game translates actions to commands&lt;&#x2F;li&gt;
&lt;li&gt;Game apply commands to its state&lt;&#x2F;li&gt;
&lt;li&gt;Game send commands to listeners (UIs)&lt;&#x2F;li&gt;
&lt;li&gt;UIs apply commands to their state&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To something like:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Game wait for actions&lt;&#x2F;li&gt;
&lt;li&gt;UI send the action after a user click&lt;&#x2F;li&gt;
&lt;li&gt;Game translate actions to events, calculating the next state (for example new player position, new round number etc)&lt;&#x2F;li&gt;
&lt;li&gt;Game interpret the events to update its state&lt;&#x2F;li&gt;
&lt;li&gt;Game send events to listeners&lt;&#x2F;li&gt;
&lt;li&gt;Listeners interpret the events to update their state&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The event schema can be quite simple, for example a tuple with the event keyword
and an optional map holding the information attached to it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;player-moved-event &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:player-moved &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target 0 :old-position &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:row 0 :col 0&lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:new-position &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:row 0 :col 1&lt;&#x2F;span&gt;&lt;span&gt;}}])
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Easy to build, easy to destructure.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;one-example&quot;&gt;One example&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s have a look at a concrete example, currently the only available action
is &lt;code&gt;:move-player&lt;&#x2F;code&gt; so yeah we don&#x27;t much choice :)&lt;&#x2F;p&gt;
&lt;p&gt;The action itself is simple:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction :east :target 0&lt;&#x2F;span&gt;&lt;span&gt;}]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It means that the user 0 can move to east, amazing! If the game logic will
receive this action it will generate two events, &lt;code&gt;:player-moved&lt;&#x2F;code&gt; to signal
the new position of the user and &lt;code&gt;round-started&lt;&#x2F;code&gt; to signal that a new round
has started. For now the function that translates actions to events looks like
this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;action-&amp;gt;events
&lt;&#x2F;span&gt;&lt;span&gt;  [game action]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prn &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Triggered action&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; action)
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; action
&lt;&#x2F;span&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player&lt;&#x2F;span&gt;&lt;span&gt; opts] [(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;player-moved-event&lt;&#x2F;span&gt;&lt;span&gt; game opts)
&lt;&#x2F;span&gt;&lt;span&gt;                              (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;round-started-event&lt;&#x2F;span&gt;&lt;span&gt; game)]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each event will describe what happened, the game logic will do its best to
reflect the change to its state and the UI will interpret the event to show
the change to the user; it is more of a mindset that is applied to both layers
and this helps to keep both worlds separated. Maybe it is worth having a look at
how these first two event generators are implemented:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;player-moved-event
&lt;&#x2F;span&gt;&lt;span&gt;  [{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[players]}
&lt;&#x2F;span&gt;&lt;span&gt;   {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[direction target]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[old-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; players [target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;])
&lt;&#x2F;span&gt;&lt;span&gt;        new-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-position&lt;&#x2F;span&gt;&lt;span&gt; old-pos direction)]
&lt;&#x2F;span&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:player-moved &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target&lt;&#x2F;span&gt;&lt;span&gt; target
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:old-position&lt;&#x2F;span&gt;&lt;span&gt; old-pos
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:new-position&lt;&#x2F;span&gt;&lt;span&gt; new-pos}]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;round-started-event
&lt;&#x2F;span&gt;&lt;span&gt;  [{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[turn players]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[current-player round-number]} turn]
&lt;&#x2F;span&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:round-started &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:current-player &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; current-player) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt; players))
&lt;&#x2F;span&gt;&lt;span&gt;                     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:round-number &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; round-number)}]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now that we have events these must be interpreted to reflect these changes
to the respective states, let&#x27;s start with the game side:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle-event-player-moved
&lt;&#x2F;span&gt;&lt;span&gt;  [game {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[target new-position]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc-in&lt;&#x2F;span&gt;&lt;span&gt; game [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;] new-position))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle-round-started
&lt;&#x2F;span&gt;&lt;span&gt;  [game turn]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc&lt;&#x2F;span&gt;&lt;span&gt; game &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:turn&lt;&#x2F;span&gt;&lt;span&gt; turn))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle-event
&lt;&#x2F;span&gt;&lt;span&gt;  [game event]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prn &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Received event &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; event)
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; event
&lt;&#x2F;span&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:player-moved&lt;&#x2F;span&gt;&lt;span&gt; opts] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;handle-event-player-moved&lt;&#x2F;span&gt;&lt;span&gt; game opts)
&lt;&#x2F;span&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:round-started&lt;&#x2F;span&gt;&lt;span&gt; opts] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;handle-round-started&lt;&#x2F;span&gt;&lt;span&gt; game opts)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;handle-event&lt;&#x2F;code&gt; will dispatch to the proper function which then will
update the game state. One thing I like about this approach is that
it makes state transitions quite straightforward at every layer.&lt;&#x2F;p&gt;
&lt;p&gt;p.s. Yes yes, the name of the functions is not consistent at all, I will take
care of this in future.&lt;&#x2F;p&gt;
&lt;p&gt;How does it look on the frontend side? Different! Which is the point of
this change, here is how &lt;code&gt;player-moved&lt;&#x2F;code&gt; is handled in the UI:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;event--player-moved
&lt;&#x2F;span&gt;&lt;span&gt;  [game {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[target old-position new-position]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; game
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; old-position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] disj target)
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; new-position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] conj target)
&lt;&#x2F;span&gt;&lt;span&gt;      ))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So what is the difference? At the game logic layer we just need to update
the position in the player map and at the UI layer we &amp;quot;put&amp;quot; the player id
in the new tile and we remove it from the previous position.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;In this short post we have explored how to make the game and UI states really
independent of each other. Still, the initial map is the same but then it will
evolve following two distinct paths. It is not just a cosmetic change but it 
creates the baseline for different client implementations, for example I am 
curious to try PixiJS (again) or even Three.js and this setup would make it
possible to just work on the UI layer without touching the game logic.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;18liy32&#x2F;mazeboard_4_moving_away_from_commands_to_events&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_intro-activity-7142615425074233345-xpd1?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;x.com&#x2F;focaskater&#x2F;status&#x2F;1736850256329920614?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;code&quot;&gt;Code&lt;&#x2F;h1&gt;
&lt;p&gt;This post include changes made in two different merge requests:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;first switch to events (&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;merge_requests&#x2F;7&quot;&gt;MR&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;build the UI state gradually using events (&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;merge_requests&#x2F;8&quot;&gt;MR&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Mazeboard 3 - core.async to update the UI layer</title>
          <pubDate>Sat, 09 Dec 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;In the last &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;post&lt;&#x2F;a&gt;
we have started decoupling game and UI logic in order to be able
to run the two layers in different processes; we are not there
yet and it will take some time to get to the final client&#x2F;server
setup, but we will get there, eventually :)&lt;&#x2F;p&gt;
&lt;p&gt;We have moved some logic and the shared state to the game layer,
even if the UI is still using the state atom from the &lt;code&gt;mazeboard.game&lt;&#x2F;code&gt;
namespace, actions are sent to game layer using &lt;code&gt;core.async&lt;&#x2F;code&gt; instead
of calling the update functions directly; it is not much but it is
honest work.&lt;&#x2F;p&gt;
&lt;p&gt;Goal of this post is to further decouple UI and game logic by having
a separate state for both layers and propagate changes from the game
layer to the UI via &lt;code&gt;core.async&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;recap-of-the-previous-changes&quot;&gt;Recap of the previous changes&lt;&#x2F;h1&gt;
&lt;p&gt;We started from something like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;UI is rendered using dumdom components driven by a map describing the game state&lt;&#x2F;li&gt;
&lt;li&gt;DOM events like clicks are handled by dumdom&#x27;s global event handler, again data driven&lt;&#x2F;li&gt;
&lt;li&gt;data associated with events describe actions that the player wants to do like moving in the board&lt;&#x2F;li&gt;
&lt;li&gt;actions are handled by directly calling the game layer which updates the global state&lt;&#x2F;li&gt;
&lt;li&gt;the UI is subscribed to the global state and re-renders when the state atom changes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And we ended up with the new setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the shared have been moved from &lt;code&gt;maeboard.ui.core&lt;&#x2F;code&gt; to &lt;code&gt;mazeboard.game&lt;&#x2F;code&gt; namespace&lt;&#x2F;li&gt;
&lt;li&gt;actions are sent to the game layer using a &lt;code&gt;core.async&lt;&#x2F;code&gt; channel which the game layer listens to, to break the direct call dependency&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The UI still listen to the game state atom directly, and we are going to fix this.&lt;&#x2F;p&gt;
&lt;p&gt;The commit is available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;commit&#x2F;11de4bafb1a7ea65addfafbf957af10febc9f8f1&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;two-layers-two-state-atoms&quot;&gt;Two layers, two state atoms&lt;&#x2F;h1&gt;
&lt;p&gt;We want to use two distinct state atoms for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;managing and mutating the game state according to requested actions&lt;&#x2F;li&gt;
&lt;li&gt;update the UI state that drives the rendering of the game board&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The most obvious thing to do would be to keep the pre existing game
state atom in the &lt;code&gt;mazeboard.game&lt;&#x2F;code&gt; namespace and a dedicated state 
atom to &lt;code&gt;mazeboard.ui.core&lt;&#x2F;code&gt; namespace so that the two layers can 
happily live their independent lives :) &lt;&#x2F;p&gt;
&lt;p&gt;This is not enough, because we want to be able to update the UI state
after something happened in the game layer; this must somehow be communicated
to the UI layer via &lt;code&gt;core.async&lt;&#x2F;code&gt; channels but given that now we are directly
&amp;quot;assoc&amp;quot;ing or &amp;quot;update(-in)&amp;quot;ing directly to the game state map, it is not easy
to transfer the intended changes between layers.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s do a step back, or sideways, to try do define a data format that 
can describe state changes that can be transferred between layers and
applied accordingly to both states.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;abstracting-state-changes&quot;&gt;Abstracting state changes&lt;&#x2F;h1&gt;
&lt;p&gt;We can start by having a look at how the action that handles player&#x27;s 
movement is implemented:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;action-move-player
&lt;&#x2F;span&gt;&lt;span&gt;  [{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[current-player players] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; game} {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[direction target]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[old-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; players [target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;])
&lt;&#x2F;span&gt;&lt;span&gt;        new-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-position &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; game [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;]) direction)]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; game
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;] new-pos)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:current-player &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; current-player) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt; players)))
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; old-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] disj target)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; new-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] conj target)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:round-number&lt;&#x2F;span&gt;&lt;span&gt; inc))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All it is doing is to take the game map, and applying &lt;code&gt;assoc(-in)&lt;&#x2F;code&gt; and 
&lt;code&gt;update(-in)&lt;&#x2F;code&gt; functions to it; &lt;code&gt;update&lt;&#x2F;code&gt; and &lt;code&gt;update-in&lt;&#x2F;code&gt; can be replaced by
their assoc forms if handled correctly so, if we can replace the direct calls
to assoc(-in) functions to data then we then can abstract away what we want to do
using just data in a form the looks something like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;] new-pos]
&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; old-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] old-tile-players]
&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; new-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] new-tile-players]
&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc :current-player &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; current-player) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt; players))]
&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc :round-number &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; round-number)]]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can then have a function that takes a data structure, the requested
changes and that can spit out a new, updated data structure:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;apply-commands
&lt;&#x2F;span&gt;&lt;span&gt;  [state commands]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;reduce
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[game command]
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; command
&lt;&#x2F;span&gt;&lt;span&gt;            [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc&lt;&#x2F;span&gt;&lt;span&gt; key value] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc&lt;&#x2F;span&gt;&lt;span&gt; game key value)
&lt;&#x2F;span&gt;&lt;span&gt;            [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:assoc-in&lt;&#x2F;span&gt;&lt;span&gt; path value] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc-in&lt;&#x2F;span&gt;&lt;span&gt; game path value)))
&lt;&#x2F;span&gt;&lt;span&gt;    state commands))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With all these building blocks in place what we have left to do is to
update our code to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;transform actions (like playing a move) to commands to update the game state&lt;&#x2F;li&gt;
&lt;li&gt;broadcast all these commands to interested parties (game logic and UI)&lt;&#x2F;li&gt;
&lt;li&gt;apply the commands to reflect the changes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The final result can be seen &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;merge_requests&#x2F;6&#x2F;diffs?commit_id=ad58453271ce32cf2ef5bbdb0bd329476cef2949&quot;&gt;here&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Changes can be summarized as following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Game and UI layers have their own state&lt;&#x2F;li&gt;
&lt;li&gt;On start, the initial game state is used in both layers&lt;&#x2F;li&gt;
&lt;li&gt;On user actions a new changeset, in form of commands, is generated&lt;&#x2F;li&gt;
&lt;li&gt;The commands are applied to the game state&lt;&#x2F;li&gt;
&lt;li&gt;The commands are sent to the UI layer and applied there too&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;We have started with a tightly coupled core and UI logic code base,
slowly we have pushed to the edges the concerns that are more suited 
to each layer; communication between layers is based on &lt;code&gt;core.async&lt;&#x2F;code&gt; to
help us to reason about intents (user actions or state changes) and we
have closed the circle with a setup that leverages the abstractions that
we have built to give us the possibility to reason about game and UI independently.&lt;&#x2F;p&gt;
&lt;p&gt;It is not 100% evident at this point, but the UI and game state are still sharing 
way too much structure:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;there is no reason why the game state must be concerned with actions available in a &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;blob&#x2F;main&#x2F;src&#x2F;cljs&#x2F;mazeboard&#x2F;ui&#x2F;components&#x2F;game.cljs?ref_type=heads#L9&quot;&gt;tile&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;there is no reason why the UI state must be concerned by the &lt;code&gt;:players&lt;&#x2F;code&gt; key in the game map, given that it does not make any use of it&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This will be addressed in the next post, but for now I am happy with what we
have achieved!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Mazeboard 2 - Using core.async to decouple game and UI logic</title>
          <pubDate>Tue, 05 Dec 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;The previous &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;post&lt;&#x2F;a&gt; ended
by looking at the shortcomings of having the game and UI logic completely coupled,
which is not ideal in a client&#x2F;server game setup. We want to setup our code to make it 
easy to reason about the two layers separately, focusing on their specificity and,
of course, we want to prepare it make it possible for the two layers to communicate
over a network connection.&lt;&#x2F;p&gt;
&lt;p&gt;Here is the plan:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;try not to break the ability to show the board and trigger one action (you know, for confidence)&lt;&#x2F;li&gt;
&lt;li&gt;move the app state to another namespace to highlight dependencies&lt;&#x2F;li&gt;
&lt;li&gt;break the direct call graph by introducing a sorts of communication layer&lt;&#x2F;li&gt;
&lt;li&gt;let the game to logic handles actions&lt;&#x2F;li&gt;
&lt;li&gt;UI logic applies changes directed by the core game logic&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;move-slowly-without-breaking-everything&quot;&gt;Move slowly without breaking everything&lt;&#x2F;h1&gt;
&lt;p&gt;Baby steps. I am not Meta and I cannot move fast and break everything ;)&lt;&#x2F;p&gt;
&lt;p&gt;One thing to keep in mind is that everything is still running inside the
same process in the browser, but we want to slowly separate client and
server concerns so that when the server will run in a remote process then
the client will not need any fundamental changes to keep it working.&lt;&#x2F;p&gt;
&lt;p&gt;The current setup has a global state that represents the game (players,
board with the tiles, current turn and possible actions) and it is used
to render the UI components and handle the game logic altogether; before
splitting the two concerns lets move this state elsewhere so that we can
start to identify the dependencies between the two layers. Here is
the new namespace that will hold the game logic, &lt;code&gt;mazeboard.game&lt;&#x2F;code&gt;: &lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.game
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[cljs.core.async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; async]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.actions &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; actions]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.test-data &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; test-data])
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require-macros &lt;&#x2F;span&gt;&lt;span&gt;[cljs.core.async.macros &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[go-loop]]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defonce &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;state &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span&gt;{}))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;game-loop
&lt;&#x2F;span&gt;&lt;span&gt;  [actions-chan]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;go-loop &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[action (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async&#x2F;&amp;lt;!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan)]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state update &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game&lt;&#x2F;span&gt;&lt;span&gt; actions&#x2F;dispatch action))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;recur&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;init &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[actions-chan (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async&#x2F;chan&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        events-chan (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async&#x2F;chan&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test-data&#x2F;create-game&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;game-loop&lt;&#x2F;span&gt;&lt;span&gt; actions-chan)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    [events-chan actions-chan]))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The namespace contains a little bit more that just moving the state from
the UI layer to the logic layer, let&#x27;s break it down:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;state&lt;&#x2F;code&gt; atom: holds the game state and the UI, for now, will subscribe to its changes&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;init&lt;&#x2F;code&gt; fn: fills the &lt;code&gt;state&lt;&#x2F;code&gt; atom with game data and returns two &lt;code&gt;core.async&lt;&#x2F;code&gt; channels to communicate between the two layers&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;game-loop&lt;&#x2F;code&gt; fn: will listen for actions sent to the &lt;code&gt;actions-chan&lt;&#x2F;code&gt; channel and will update the game state accordingly, using the &lt;code&gt;mazeboard.actions&#x2F;dispatch&lt;&#x2F;code&gt; fn introduced in the previous post&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;code&gt;events-chan&lt;&#x2F;code&gt; channel is not used at the moment but, in future, listeners of this channel will be able to update their state after changes of the game state&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The namespace &lt;code&gt;mazeboard.ui.core&lt;&#x2F;code&gt; requires few changes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;remove (at least for now) its own state atom and watch the game state atom to render changes&lt;&#x2F;li&gt;
&lt;li&gt;update the global dumdom event handler (for user actions) to send actions to the &lt;code&gt;actions-chan&lt;&#x2F;code&gt; channel instead of calling the &lt;code&gt;dispatch&lt;&#x2F;code&gt; action directly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here is how the event handler setup look like now:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[[events-chan actions-chan] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;game&#x2F;init&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;d&#x2F;set-event-handler!
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_e actions]
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[action actions]
&lt;&#x2F;span&gt;&lt;span&gt;         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;put!&lt;&#x2F;span&gt;&lt;span&gt; actions-chan action)))))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Instead of subscribing to the &lt;code&gt;mazeboard.game&#x2F;state&lt;&#x2F;code&gt; atom directly it would have
been cleaner to return it from &lt;code&gt;maeboard.game&#x2F;init&lt;&#x2F;code&gt; fn and subscribe to that but
this setup is going to be changed soon so we can close an eye this time ;)&lt;&#x2F;p&gt;
&lt;p&gt;After these changes the game renders and actions are handled correctly, baby steps!
Unfortunately there is still this one shared state that we want to get rid of, but
this will be covered in the next post.&lt;&#x2F;p&gt;
&lt;p&gt;The full change set for this post is available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;-&#x2F;commit&#x2F;11de4bafb1a7ea65addfafbf957af10febc9f8f1&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;In this short post we have started the decoupling process of UI and game logic layers,
even if not completely, by using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.async&quot;&gt;core.async&lt;&#x2F;a&gt;
as an abstraction of a communication layer.
The UI is still subscribed to a shared state (atom) to render updates but
at least the user actions are now sent &amp;quot;somewhere&amp;quot; instead of a direct function
call, preparing it for the next step of receiving game state updates using a 
similar approach.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;18bjfwf&#x2F;clojure_bites_maeboard_2using_coreasync_to&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Mazeboard 1 - Dumdom event handler</title>
          <pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-1-dumdom-event-handler/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-1-dumdom-event-handler/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-1-dumdom-event-handler/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;In the &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;previous&lt;&#x2F;a&gt; post I&#x27;ve introduced the
game I am working on, presenting the stack and the general code structure
I&#x27;d like to follow, I&#x27;ve closed the post with a couple of screenshot of a static
game board, now it is time to make it a bit more interactive.&lt;&#x2F;p&gt;
&lt;p&gt;The goal is to let the user to click on one of the arrows (which represent the
available actions, more actions to come later) and move the player icon to correct
new position.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-game-ui-state&quot;&gt;The game UI state&lt;&#x2F;h1&gt;
&lt;p&gt;The UI state is a map that holds the data that drives the rendering of the components,
and includes information about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the boards with its own tiles, tiles can also include which action can be performed on them&lt;&#x2F;li&gt;
&lt;li&gt;the players with name and position&lt;&#x2F;li&gt;
&lt;li&gt;the current player&lt;&#x2F;li&gt;
&lt;li&gt;the current round&lt;&#x2F;li&gt;
&lt;li&gt;the end position&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The map will be updated after player actions and changes will be reflected in the interface
by re-rendering what have changed. To achieve this goal the UI state will be stored in an atom,
attaching the rendering function to the atom watch list, something like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.core
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[dumdom.core &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; d]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.test-data &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; test-data]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.ui.components.app &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; app]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defonce &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;state_ &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span&gt;{}))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;render &lt;&#x2F;span&gt;&lt;span&gt;[state]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;d&#x2F;render &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;app&#x2F;App&lt;&#x2F;span&gt;&lt;span&gt; state)
&lt;&#x2F;span&gt;&lt;span&gt;            (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;js&#x2F;document.getElementById &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;init &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;add-watch&lt;&#x2F;span&gt;&lt;span&gt; state_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:app &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_ _ _ new-state] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span&gt; new-state)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state_ assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test-data&#x2F;create-game&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;create-game&lt;&#x2F;code&gt; returns a map which is an implementation detail and can be ignored.
With this setup, each time the state_ atom will be mutated, the rendering function will
be called with the new state, causing a change in the UI. At this time the only way to 
make changes to the UI state is to manually &lt;code&gt;swap!&lt;&#x2F;code&gt; or &lt;code&gt;reset!&lt;&#x2F;code&gt; the atom, so not ideal
for a real game, but it is enough get started; this approach can be useful to manually
test ideas in the REPL until we are sure how something will be implemented, creating a
nice interactive development environment.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;triggering-actions-on-click-events&quot;&gt;Triggering actions on click events&lt;&#x2F;h1&gt;
&lt;p&gt;After few iterations, playing with the game UI (man, I suck at CSS), I can shows where
the players are in the board and what the player &amp;quot;One&amp;quot; can do i.e. move south or east,
here is how it looks currently:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;mazeboard-move-actions.png&quot; alt=&quot;Board&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Clicking on one of the arrows should trigger an action that will update the player&#x27;s
position, change the current player, update the round number and update the UI to
reflect what has changed. First step is to handle clicks on the arrows.&lt;&#x2F;p&gt;
&lt;p&gt;Dumdom provides two ways to attach &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;dumdom&#x2F;#event-listeners&quot;&gt;event handlers&lt;&#x2F;a&gt;
to DOM elements, specifying the key &lt;code&gt;:on-click&lt;&#x2F;code&gt; in the element configuration map:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;if the value of the key is a function then it will be called directly&lt;&#x2F;li&gt;
&lt;li&gt;if the value is anything else (for example lists, vects, maps) then a global handler will be called&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The global event handler must be registered with &lt;code&gt;dumdom.core&#x2F;set-event-handler!&lt;&#x2F;code&gt; whose only parameter
is a function that will receive the target element and whatever data has been specified in the
&lt;code&gt;:on-click&lt;&#x2F;code&gt; value of the element. The event handler function have a signature like &lt;code&gt;(fn [target data])&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Both methods (fn or data) have use cases that can be more or less suitable
depending on your specific needs, in this case I have opted for the data
driven approach for two reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;it feels more declarative, the element &amp;quot;tells&amp;quot; what it wants to do&lt;&#x2F;li&gt;
&lt;li&gt;it makes it possible to attach actions to elements from the game logic that can be later used by the UI; especially useful when the game logic will be handled by a separate (remote) process&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Lets re-write the init method to setup an event handler:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;init &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;add-watch&lt;&#x2F;span&gt;&lt;span&gt; state_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:app &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_ _ _ new-state] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span&gt; new-state)))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;d&#x2F;set-event-handler!
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_e actions]
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[action actions]
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prn&lt;&#x2F;span&gt;&lt;span&gt; action))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state_ assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test-data&#x2F;create-game&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And here is how the &lt;code&gt;Tile&lt;&#x2F;code&gt; component looks like, with data driven &lt;code&gt;:on-click&lt;&#x2F;code&gt; trigger:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defcomponent &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Tile &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[actions position end-position? players]} all-players]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cond-&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:div.tile &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:class &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;tile-background&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;                      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:key&lt;&#x2F;span&gt;&lt;span&gt; position}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;           (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[on-click action-class]} actions]
&lt;&#x2F;span&gt;&lt;span&gt;             [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:div.action &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:on-click&lt;&#x2F;span&gt;&lt;span&gt; on-click &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:class&lt;&#x2F;span&gt;&lt;span&gt; action-class}])
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;           (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;[player players]
&lt;&#x2F;span&gt;&lt;span&gt;             (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[class name]} (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span&gt; all-players player)]
&lt;&#x2F;span&gt;&lt;span&gt;               [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:div.player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:class&lt;&#x2F;span&gt;&lt;span&gt; class} name]))]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    end-position?
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;conj &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:div.end-position&lt;&#x2F;span&gt;&lt;span&gt;])))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; example of component&amp;#39;s actions
&lt;&#x2F;span&gt;&lt;span&gt;#_[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:on-click &lt;&#x2F;span&gt;&lt;span&gt;[[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction :east :target 0&lt;&#x2F;span&gt;&lt;span&gt;}]]
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:action-class &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;action-move-east&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;   {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:on-click &lt;&#x2F;span&gt;&lt;span&gt;[[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction :south :target 0&lt;&#x2F;span&gt;&lt;span&gt;}]]
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:action-class &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;action-move-south&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Dumdom does not impose any structure to the data that can be associated to
an element&#x27;s event handler so I have taken the examples in Parens of the dead
and I will structure the actions as a vector of vectors, with the first element
of the sub vectors describing the action as a keyword and the second element as
the options&#x2F;parameters map of the action itself.&lt;&#x2F;p&gt;
&lt;p&gt;After setting up the global event handler, if we click on one the arrows, for
example to go east, in the browser console log we should see &lt;code&gt;[:move-player {:direction :east :target 0}]&lt;&#x2F;code&gt;,
meaning that the event handler is working properly. But still, nothing is happening
in the game UI, lets fix that.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;dispatching-actions-and-state-updates&quot;&gt;Dispatching actions and state updates&lt;&#x2F;h1&gt;
&lt;p&gt;We are now able to trigger actions and we need something that can update the game
state and reflect the changes to screen. In this case I think it is better jump
straight to the code that will move a player in Mazeboard:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.actions
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[clojure.core.match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[match]]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;update-position
&lt;&#x2F;span&gt;&lt;span&gt;  [position direction]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;case&lt;&#x2F;span&gt;&lt;span&gt; direction
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:north &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt; position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:row&lt;&#x2F;span&gt;&lt;span&gt; dec)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:east &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt; position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:col&lt;&#x2F;span&gt;&lt;span&gt; inc)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:south &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt; position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:row&lt;&#x2F;span&gt;&lt;span&gt; inc)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:west &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt; position &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:col&lt;&#x2F;span&gt;&lt;span&gt; dec)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;action-move-player
&lt;&#x2F;span&gt;&lt;span&gt;  [{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[current-player players] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; game} {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[direction target]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[old-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; players [target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;])
&lt;&#x2F;span&gt;&lt;span&gt;        new-pos (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-position &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-in&lt;&#x2F;span&gt;&lt;span&gt; game [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;]) direction)]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; game
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt; target &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:position&lt;&#x2F;span&gt;&lt;span&gt;] new-pos)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:current-player &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; current-player) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt; players)))
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; old-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] disj target)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update-in &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:board :tiles&lt;&#x2F;span&gt;&lt;span&gt; new-pos &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players&lt;&#x2F;span&gt;&lt;span&gt;] conj target)
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;update &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:round-number&lt;&#x2F;span&gt;&lt;span&gt; inc))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;dispatch
&lt;&#x2F;span&gt;&lt;span&gt;  [game action]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prn &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Triggered action&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; action)
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; action
&lt;&#x2F;span&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player&lt;&#x2F;span&gt;&lt;span&gt; opts] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;action-move-player&lt;&#x2F;span&gt;&lt;span&gt; game opts)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The code must be read bottom up, first thing to look at is the &lt;code&gt;dispatch&lt;&#x2F;code&gt; function
that will match the requested action to a specific function calling it; the fact the
here &lt;code&gt;clojure.core.match&lt;&#x2F;code&gt; is being used is an implementation detail, you may prefer
a different approach (for example &lt;code&gt;defmulti&lt;&#x2F;code&gt; is another good candidate). This is quite
simple, we try to match the requested action to something that we know how to handle,
in this case moving the player. &lt;code&gt;action-move-player&lt;&#x2F;code&gt; will, of course, update the game
(and UI) state, returning it.&lt;&#x2F;p&gt;
&lt;p&gt;There is still one bit missing, who is calling &lt;code&gt;dispatch&lt;&#x2F;code&gt; and how this will update
the global state atom? We have to update the global handler to connect actions to
state updates, here is how the init fn is looking now:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;init &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;d&#x2F;set-event-handler!
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_e actions]
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[action actions]
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state_ update &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game&lt;&#x2F;span&gt;&lt;span&gt; actions&#x2F;dispatch action))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;add-watch&lt;&#x2F;span&gt;&lt;span&gt; state_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:app &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_ _ _ state] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span&gt; state)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; state_ assoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test-data&#x2F;create-game&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After these changes, when clicking one of the arrows the player icon will
be placed correctly in the next tile, in this screenshot we can see the effect
of moving south&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;mazeboard-1-player-moved.png&quot; alt=&quot;Moved south&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Together with the updated player &amp;quot;One&amp;quot; position we can also observe that the round
number has changed and that now it is the turn of player &amp;quot;Two&amp;quot;; updating the tiles
to reflect what the other user can do will be the content of another post.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;future-improvements&quot;&gt;Future improvements&lt;&#x2F;h1&gt;
&lt;p&gt;At this point we have a working application that can react to user actions and render
the updated state accordingly. What I don&#x27;t like about the current approach is that it
is coupling the game logic and the UI logic, for example knowing which actions are available
and how to render them is a concern of the UI state and not of the game state, but now
everything is mixed together and soon managing both the game and the UI will be a
complete mess and hard to maintain.
To make a concrete example here is how the tile is represented at the moment:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;tile &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:actions &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:on-click &lt;&#x2F;span&gt;&lt;span&gt;[[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction :east :target 0&lt;&#x2F;span&gt;&lt;span&gt;}]]
&lt;&#x2F;span&gt;&lt;span&gt;                      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:action-class &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;action-move-east&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;                     {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:on-click &lt;&#x2F;span&gt;&lt;span&gt;[[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:move-player &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:direction :south :target 0&lt;&#x2F;span&gt;&lt;span&gt;}]]
&lt;&#x2F;span&gt;&lt;span&gt;                      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:action-class &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;action-move-south&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}]
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:walls &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:closed :open :open :closed&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:end-position? true
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:players &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]})
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;:actions&lt;&#x2F;code&gt;, &lt;code&gt;:end-position?&lt;&#x2F;code&gt; and &lt;code&gt;:players&lt;&#x2F;code&gt; keys are closely related to the UI, instead
the &lt;code&gt;:walls&lt;&#x2F;code&gt; key is more on the game logic side.&lt;&#x2F;p&gt;
&lt;p&gt;By separating game and UI I hope to get to a setup were:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the UI will trigger actions that will be sent to the game layer&lt;&#x2F;li&gt;
&lt;li&gt;the game layer will validate the requested action and, if valid, will update the game state&lt;&#x2F;li&gt;
&lt;li&gt;after updating the game state, events will be triggered for all clients describing what has happened&lt;&#x2F;li&gt;
&lt;li&gt;all clients will update their state using the received events and re-render the UI accordingly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A bonus benefit of this kind of separation of concerns is that unit testing the game logic and
the event generation that will feed the UI will cover most of the application!
(Thanks &lt;a href=&quot;https:&#x2F;&#x2F;www.parens-of-the-dead.com&#x2F;&quot;&gt;Parens of the dead&lt;&#x2F;a&gt; for this idea).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;In this post we have seen how to model the UI state (even if coupled with game state),
how to dispatch actions from DOM elements and how to handle them to update the game.&lt;&#x2F;p&gt;
&lt;p&gt;The whole setup is quite primitive but good enough for a starting point. I am very
excited to finally decouple game and UI concerns, it will make it easier to build
game and UI logic separately.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;187knke&#x2F;clojure_bytes_dumdom_event_handlers&#x2F;?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;x.com&#x2F;focaskater&#x2F;status&#x2F;1730242382220726414?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;clojurians.slack.com&#x2F;archives&#x2F;C8NUSGWG6&#x2F;p1701357227701809&quot;&gt;Clojurians Slack&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;sources&quot;&gt;Sources&lt;&#x2F;h1&gt;
&lt;p&gt;Mazeboard source code is available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Mazeboard 0</title>
          <pubDate>Sat, 18 Nov 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-bites-mazeboard-0/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mazeboard-0/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mazeboard-0/">&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h1&gt;
&lt;p&gt;Welcome back to Clojure Bites!&lt;&#x2F;p&gt;
&lt;p&gt;This is a new format compared to the previous issues, instead of focusing
on one specific topic, it will be more like a diary of what I am learning while
resurrecting an old game I was working on some time ago. After spending
a significant amount of time on the backend, I was stuck at the user facing part,
basically for two reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Lack of experience in frontend development&lt;&#x2F;li&gt;
&lt;li&gt;Not knowing how to use WebSockets or SSE properly, especially how to structure the backend&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first problem was not a big one, with the help of &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tonsky&#x2F;rum&quot;&gt;Rum&lt;&#x2F;a&gt;
as the React wrapper and &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;citrus&quot;&gt;Citrus&lt;&#x2F;a&gt; for state management
I was making progress even if slowly. The realtime communication part instead was a big
blocker, it is ok if the frontend sucks as long as you can play the game! Time passed, I&#x27;ve
lost interest and the project has been parked until now; at least I&#x27;ve gained a bit of
experience with client&#x2F;server realtime communication, lets see how far I will get this time :)&lt;&#x2F;p&gt;
&lt;p&gt;The plan now is to, mostly, start from from scratch and implement the game
focusing on the frontend first, and back again to the backed later.
A great source of inspiration is &lt;a href=&quot;https:&#x2F;&#x2F;www.parens-of-the-dead.com&#x2F;&quot;&gt;Parens of the dead&lt;&#x2F;a&gt;,
I like how they have structured their code to have a thin (dump?) client backed by a fully
tested logic implemented in the backend; if something will look wrong or dumb in my
implementation I&#x27;ll be the guy to blame, not them ;) &lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-game&quot;&gt;The game&lt;&#x2F;h1&gt;
&lt;p&gt;The game should be a sorts of rogue like board game where players explore a maze,
with the goal to find a treasure and bring it back to to starting position.
The board is composed by squares in a NxN grid, each square have randomly generated
connections to nearby squares which can be used to move towards the treasure and back.
Each turn players will flip a coin (or roll a dice) to get the possible action,
move to a next square or rotate a square of the board (useful to make it harder to 
reach the goal to other players).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;frontend-stack&quot;&gt;Frontend stack&lt;&#x2F;h1&gt;
&lt;p&gt;The first change from the previous attempt is the build system, switching from
&lt;a href=&quot;https:&#x2F;&#x2F;figwheel.org&#x2F;&quot;&gt;figwheel-main&lt;&#x2F;a&gt; to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;thheller&#x2F;shadow-cljs&quot;&gt;shadow-cljs&lt;&#x2F;a&gt;.
The main reason is that, to my understanding, it is easier to work with JS libraries
with shadow that with figwheel; in another project I was using PixiJS and the version
available in cljsjs repo was a bit outdated and I don&#x27;t want to be in this situation again.&lt;&#x2F;p&gt;
&lt;p&gt;Having covered the build system it is now time to pick something to render the game.
The first version of this project was built on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tonsky&#x2F;rum&#x2F;&quot;&gt;Rum&lt;&#x2F;a&gt;
because it felt simple enough, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;reagent-project&#x2F;reagent&quot;&gt;Reagent&lt;&#x2F;a&gt;
was another (more popular) option that for some reason did not click for me, I totally
overlooked &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;levand&#x2F;quiescent&quot;&gt;Quiescent&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;omcljs&#x2F;om&#x2F;&quot;&gt;Om(next)&lt;&#x2F;a&gt;.
Newer candidates in this space are &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lilactown&#x2F;helix&quot;&gt;Helix&lt;&#x2F;a&gt; or
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pitch-io&#x2F;uix&quot;&gt;UIx&lt;&#x2F;a&gt; if you after mature and up to date React wrappers,
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;dumdom&#x2F;&quot;&gt;dumdom&lt;&#x2F;a&gt; if you want to stick to virtual dom but
you are not interested&#x2F;experienced in React, and finally &lt;a href=&quot;https:&#x2F;&#x2F;htmx.org&#x2F;&quot;&gt;htmx&lt;&#x2F;a&gt; if you
want to stick to the hypermedia concept. All are strong and valid options for one reason
or another but again I want to favor simplicity and dumdom being mostly focused on the
rendering part, looks like the best candidate for my needs.&lt;&#x2F;p&gt;
&lt;p&gt;Last bit of tooling I am introducing to my stack is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;portfolio&#x2F;&quot;&gt;Portfolio&lt;&#x2F;a&gt;,
which is a sorts of &lt;a href=&quot;https:&#x2F;&#x2F;storybook.js.org&#x2F;&quot;&gt;Storybook&lt;&#x2F;a&gt; for ClojureScript, in few
words it makes it possible to test UI components in isolation and can be used as a documentation.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;setting-up-the-development-environment&quot;&gt;Setting up the development environment&lt;&#x2F;h1&gt;
&lt;p&gt;To recap, I am using shadow-cljs as the build system, dumdom as the rendering library and
portfolio to preview&#x2F;debug components; the first step is to setup the project i.e.
create a shadow-cljs.edn:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:dependencies &lt;&#x2F;span&gt;&lt;span&gt;[[cjohansen&#x2F;dumdom &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2023.10.13&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;                [no.cjohansen&#x2F;portfolio &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2023.04.05&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;                [cider&#x2F;cider-nrepl &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.41.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;                [refactor-nrepl&#x2F;refactor-nrepl &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;3.9.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:source-paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;src&#x2F;cljs&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;src&#x2F;cljc&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; where to look for sources
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:dev-http &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;8080 &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;classpath:public&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; &amp;quot;classpath:public&amp;quot; is required to server Portfolio&amp;#39;s resources
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:nrepl &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:middleware &lt;&#x2F;span&gt;&lt;span&gt;[cider.nrepl&#x2F;cider-middleware
&lt;&#x2F;span&gt;&lt;span&gt;                      refactor-nrepl.middleware&#x2F;wrap-refactor]
&lt;&#x2F;span&gt;&lt;span&gt;         &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 50655&lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; customize the nrepl session that shadow-cljs will run 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:builds &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game
&lt;&#x2F;span&gt;&lt;span&gt;          {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target :browser
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:output-dir &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;public&#x2F;js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:asset-path &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:modules &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:init-fn&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.core&#x2F;init}
&lt;&#x2F;span&gt;&lt;span&gt;                     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:portfolio &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:init-fn&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.portfolio&#x2F;init
&lt;&#x2F;span&gt;&lt;span&gt;                                 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:depends-on &lt;&#x2F;span&gt;&lt;span&gt;#{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main&lt;&#x2F;span&gt;&lt;span&gt;}}}}}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Generally speaking this is just a common shadow-cljs config, and you should rely on
the (extensive) shadow-cljs docs to create your own, but there are a couple
of tweaks that are worth mentioning in the context of this app:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dev-http&lt;&#x2F;code&gt; key: Portfolio has its own resources that must be included to render its interface&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;nrepl&lt;&#x2F;code&gt; key: I like to run the nrepl process out of the IDE so that I connect to it with different editors&#x2F;IDEs (and survive IDE life cycle if needed), I include cider to make it work with Emacs but it is not strictly needed for other editors&#x2F;IDEs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;modules&lt;&#x2F;code&gt; key: each key in this map will generate a new JS file; there is one module for the game and one for portfolio, each with its own specific init fn&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I have setup two pages (or views), one for the game and for portfolio, the first one will serve
the game and the other one (guess what?) portfolio UI. Given the provided build settings we will
have &lt;code&gt;main.js&lt;&#x2F;code&gt; for our app code and &lt;code&gt;portfolio.js&lt;&#x2F;code&gt; for portfolio; you want to have an html file
embedding &lt;code&gt;main.js&lt;&#x2F;code&gt;for the main app and another embedding &lt;code&gt;portfolio.js&lt;&#x2F;code&gt; for (well...) the
Portfolio UI. Given that the &lt;code&gt;portfolio&lt;&#x2F;code&gt; key in &lt;code&gt;modules&lt;&#x2F;code&gt; depends on &lt;code&gt;main&lt;&#x2F;code&gt; module the custom
view (or html) for Portfolio must also include &lt;code&gt;main&lt;&#x2F;code&gt; to be able to access your app&#x27;s components, 
something like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;&amp;lt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;DOCTYPE &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;lang&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;en&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;head&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Portfolio Canvas&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;head&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;canvas&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;role&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;js&#x2F;main.js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;js&#x2F;portfolio.js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With this setup it is possible to access the game code at the root of the dev webserver
and preview components at &lt;code&gt;&#x2F;portfolio.html&lt;&#x2F;code&gt; for quick dev and tests.&lt;&#x2F;p&gt;
&lt;p&gt;It is also worth mentioning that Portfolio UI can be customized quite a bit, for now my
portfolio namespace is as as simple as referencing the scenes to render and start the UI
adding my custom CSS to render my components:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.portfolio
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[portfolio.ui &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; ui]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.ui.scenes.game-scene]
&lt;&#x2F;span&gt;&lt;span&gt;            [mazeboard.ui.scenes.profile-scene]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;::mazeboard.ui.scenes.game-scene
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;::mazeboard.ui.scenes.profile-scene
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;init
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ui&#x2F;start! &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:config &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:css-paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;css&#x2F;style.css&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;Update 2023-11-18 12:30 CET&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thomas Heller pointed out in a &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;17y1th1&#x2F;comment&#x2F;k9qvs6d&#x2F;?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button&quot;&gt;comment&lt;&#x2F;a&gt;
on Reddit that the shadow-cljs build settings are not ideal because:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;portfolio would be included in the release build&lt;&#x2F;li&gt;
&lt;li&gt;portfolio module depends on game module and this forces including both in portfolio.html and also would run the &lt;code&gt;init-fn&lt;&#x2F;code&gt; of both modules&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A new version of the &lt;code&gt;:builds&lt;&#x2F;code&gt; map is the following&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:game
&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target :browser
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:output-dir &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;public&#x2F;js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:asset-path &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:compiler-options &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:infer-externs :auto :output-feature-set :es6&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:modules &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:init-fn&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.core&#x2F;init}}}
&lt;&#x2F;span&gt;&lt;span&gt; 
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:portfolio
&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:target :browser
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:output-dir &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;public&#x2F;portfolio-js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:asset-path &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;portfolio-js&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:compiler-options &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:infer-externs :auto :output-feature-set :es6&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:modules &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:init-fn&lt;&#x2F;span&gt;&lt;span&gt; mazeboard.ui.portfolio&#x2F;init}}}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now to run&#x2F;watch both the game and portfolio we have to run&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;$ shadow-cljs watch game portfolio&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;At this point it will be possible to access the game at http:&#x2F;&#x2F;localhost:8080 and
portfolio at http:&#x2F;&#x2F;localhost:8080&#x2F;portfolio.html. This is how the game and portfolio will
look when the build will finish (sorry for the terrible graphics...)&lt;&#x2F;p&gt;
&lt;p&gt;Game
&lt;img src=&quot;&#x2F;mazeboard-ui-alpha0.png&quot; alt=&quot;Game&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Portfolio rendering a board row
&lt;img src=&quot;&#x2F;portfolio-row.png&quot; alt=&quot;Portfolio rendering a board row&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Portfolio rendering the full board
&lt;img src=&quot;&#x2F;portfolio-board.png&quot; alt=&quot;Portfolio rendering the full board&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;first-impressions&quot;&gt;First impressions&lt;&#x2F;h1&gt;
&lt;p&gt;The first objective of this reboot was to setup a frontend first development environment,
focusing on getting a view of the game board and preparing the work for future development.
So far I am quite satisfied with the result, being able to develop components in isolation
and preview them both in Portfolio and the app.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;future-work&quot;&gt;Future work&lt;&#x2F;h1&gt;
&lt;p&gt;Next step would be to setup a playable version of the game, loosely based on the approach
show in Parens of the dead but with a twist: instead of setting up a backend I am planning
to simulate the command&#x2F;event based approach solely in the frontend for now, putting the logic
in cljc files so that, when it will come the time to have a backend, I can reuse most of it.
So far the dev experience has been great and I am hyped to work on and see the results of the
next steps!&lt;&#x2F;p&gt;
&lt;p&gt;Sources of this project can be found &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mazeboard-related-posts&quot;&gt;Mazeboard related posts&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-0&#x2F;&quot;&gt;Mazeboard 0&lt;&#x2F;a&gt; - Project intro&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-1-dumdom-event-handler&#x2F;&quot;&gt;Mazeboard 1&lt;&#x2F;a&gt; - Dumdom event handler&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-2-core-async-to-separate-game-ui-logic&#x2F;&quot;&gt;Mazeboard 2&lt;&#x2F;a&gt; - Using core async to decouple UI and game logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-3-more-async-to-fully-decouple-layers&#x2F;&quot;&gt;Mazeboard 3&lt;&#x2F;a&gt; - Improving UI and game logic decoupling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-4-commands-to-events&#x2F;&quot;&gt;Mazeboard 4&lt;&#x2F;a&gt; - Replacing commands with events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mazeboard-5-more-on-actions-cljs-tests-schema-and-future-plans&#x2F;&quot;&gt;Mazeboard 5&lt;&#x2F;a&gt; - More on actions, adding tests and future multiplayer architecture&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Checkpoint 2023-11-01</title>
          <pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/checkpoint-2023-11-01/</link>
          <guid>https://fpsd.codes/blog/checkpoint-2023-11-01/</guid>
          <description xml:base="https://fpsd.codes/blog/checkpoint-2023-11-01/">&lt;h1 id=&quot;2023-11-01-what-s-up&quot;&gt;[2023-11-01] What&#x27;s up?&lt;&#x2F;h1&gt;
&lt;p&gt;Hello everyone, long time no see!&lt;&#x2F;p&gt;
&lt;p&gt;After a break I&#x27;ve finally found new topics to work on and write about but,
before jumping to the upcoming plans lets take a look of what has happened
meanwhile.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;unrefined&quot;&gt;Unrefined&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt; is the closest thing to a product that I have developed in the 
past year, it is currently used at $WORK and I have tried to understand if
it could be valuable to other teams. The feedback I&#x27;ve received was not great, the
most positive comments were &amp;quot;interesting&amp;quot; and &amp;quot;I see the value proposition&amp;quot; but 
it haven&#x27;t move past that and, worse, haven&#x27;t reached the point to start a trial with
the teams I&#x27;ve been in contact with. At least I&#x27;ve had a lot of fun working on it and
learned a lot in the process, so the glass is still half full ;)&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately I&#x27;ve realized that to move it forward it would require a non trivial
effort compared to the potential revenue I can expect from it, based on the feedback
I have received by potential users. It will be on maintenance mode for my only user,
and if someone else wants to try it, it is still free (as in beer and in speech).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;clojure-bites&quot;&gt;Clojure Bites&lt;&#x2F;h1&gt;
&lt;p&gt;The series covered simple topics, mostly targeting Clojure beginners like
myself and usually centered on the work I was doing on Unrefined. After few posts,
I&#x27;ve had an hard time finding interesting topics to cover, which is reasonable given
that I was not progressing with Unrefined and writing about something not directly
useful was feeling a bit too artificial. If I have not experienced a problem that I
wanted to solve I find it hard to give an honest perspective on the solution that I 
am covering.&lt;&#x2F;p&gt;
&lt;p&gt;This does not mean I haven&#x27;t tried, this is a list of topics I had (have) in the
backlog, loosely based on my own requirements for Unrefined, but not limited to it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Using Keycloak as an identity provider for Unrefined.&lt;&#x2F;li&gt;
&lt;li&gt;Enabling custom estimation cheat sheets in Unrefined...which required a lot of frontend work I was not ready for.&lt;&#x2F;li&gt;
&lt;li&gt;Building on the previous point I spent some time evaluating what options are available was in the frontend space, figwheel-main VS shadow-cljs, reagent VS rum VS helix&#x2F;UIx2.&lt;&#x2F;li&gt;
&lt;li&gt;Creating a service to simplify Server Sent Events solutions that scale; based on what I&#x27;ve seen in Unrefined, it might be useful to rely on a third party solution to handle SSE traffic.&lt;&#x2F;li&gt;
&lt;li&gt;Using Clerk notebooks to work on support tickets at work as a way to document the process and create a common code base.&lt;&#x2F;li&gt;
&lt;li&gt;Using Datascript as the state of an Entity Component System library for ClojureScript; this looks extremely exciting and I had a couple of games I can adapt to this approach.&lt;&#x2F;li&gt;
&lt;li&gt;Using what I&#x27;ve learned from Unrefined to, finally, move one simple game (Mazeboard, ore on this later) to the next step, enabling realtime multiplayer games and experiment with its logic.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So yeah, so many topics at hand but I did not focus on any of them, and I was stuck (and it sucks!).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h1&gt;
&lt;p&gt;Taking Unrefined out of the equation is making it easier to decide where to spend
my time, optimizing for having fun while learning something, and the winner is:
revisiting Mazeboard&#x27;s code base to finally have a way to test its gameplay, with real players, building on top of what I&#x27;ve learned in the past months.&lt;&#x2F;p&gt;
&lt;p&gt;This will cover so many topics, possibly creating material for posts targeting people
with different levels of experience in Clojure and its tools&#x2F;libraries, for example:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;thheller&#x2F;shadow-cljs&quot;&gt;Shadow CLJS&lt;&#x2F;a&gt; project&lt;&#x2F;li&gt;
&lt;li&gt;Introducing a frontend library (or two just to show different approaches, I am looking at &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;dumdom&#x2F;&quot;&gt;dumdom&lt;&#x2F;a&gt; now)&lt;&#x2F;li&gt;
&lt;li&gt;Working on UI components and previewing them with &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;portfolio&quot;&gt;portfolio&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Live multiplayer game updates using SSE&lt;&#x2F;li&gt;
&lt;li&gt;Persisting the game state (probably with some sort of Datalog DB)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I think these are reasonaly simple topics to get started again, fueled by the future
work on Mazeboard. After this step, it will be possible to focus on broader topics,
like identity management systems, scaling, logging and monitoring and who knows what.
At least at this point the basics will be covered and experience should suggest
what&#x2F;where to look at.&lt;&#x2F;p&gt;
&lt;p&gt;The plan look very interesting (at least for me) and doable, and knowing that I am
back again at experimentation VS building a product (that no one needs, anyway) is
quite exciting!
This is my sweet spot, happy to be back at it!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;mazeboard&quot;&gt;Mazeboard&lt;&#x2F;h1&gt;
&lt;p&gt;The game should be a rogue like board game where player explore a maze (I haven&#x27;t
decided on theme yet, a dungeon? An alien spaceship?), trying to get a treasure and
bring it back at the starting position, other players can move towards the goal or
prevent other to win; it should be possible to print the board pieces and play with
a normal dice or a coin (I still have the paper prototype somewhere...). It is not a
surprise that it looks boring, I&#x27;ve had the idea during a boring meeting after all! :)
Maybe it will turn out as a playable but I will never know until someone will play
with it; relying on local, in person tests, is for sure out the
equation, so online game it is!&lt;&#x2F;p&gt;
&lt;p&gt;Few years ago my go-to approach was to build the logic on a sorts of backend, just to
abandon the project when it came the time for the user facing part. This is silly!
Now I&#x27;ve leaned that having a user facing prototype is much, much more valuable, so
I am revisiting Mazeboard&#x27;s code base to quickly provide a way to play it, favoring
user facing work.&lt;&#x2F;p&gt;
&lt;p&gt;Sources will be available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;mazeboard&quot;&gt;here&lt;&#x2F;a&gt;, I am halfway
on re-doing the frontend part, at least the UI components, and I have started using
dumdom + portfolio; excluding few headaches when setting everything up, so far the
experience have been quite positive!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;Finally I am back again at the whiteboard, experimenting with ideas and libraries,
gaining experience and hopefully giving back in form of posts or live coding sessions.
(quite unlikely but I&#x27;d like to try again! ;) )&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - Profiling with Tufte</title>
          <pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-bites-profiling/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-profiling/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-profiling/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;If we have a system in production it is quite common to want
to understand how many resources it is using as a whole and for
each of its components; this information can be used to plan
how much hardware we have to provision or to optimize the costs,
identifying expensive &amp;quot;code paths&amp;quot; and planning some work to
optimize its operational costs. Another use of this information
is resource monitoring, finding performance regressions after
we have deployed some changes to our systems.&lt;&#x2F;p&gt;
&lt;p&gt;To collect this information we can use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ptaoussanis&#x2F;tufte&quot;&gt;tufte&lt;&#x2F;a&gt;, a profiling library, and
to send collected metrics to a event&#x2F;log collector we can use
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrunoBonacci&#x2F;mulog&quot;&gt;mulog&lt;&#x2F;a&gt; which makes it easy to log data instead of text messages that
we would need to parse later. (see &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-bites-mulog&#x2F;&quot;&gt;HERE&lt;&#x2F;a&gt; for an introduction to mulog)
In this post we are going to use &lt;a href=&quot;https:&#x2F;&#x2F;opensearch.org&#x2F;&quot;&gt;Opensearch&lt;&#x2F;a&gt; but the same approach
can be easily ported to other tools or even IaaS&#x2F;PaaS.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-plan&quot;&gt;The plan&lt;&#x2F;h1&gt;
&lt;p&gt;There is a lot to do, so better to lay out a plan, it is not important
if it is not super well defined and should work to help me to not go out
of the track:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Introduce tufte and apply it to some sample code.&lt;&#x2F;li&gt;
&lt;li&gt;Create an api or webpage using ring + reitit that uses the code that we are profiling.&lt;&#x2F;li&gt;
&lt;li&gt;Add a middleware to the router to log with mulog, writing to file.&lt;&#x2F;li&gt;
&lt;li&gt;Collect metrics and log them periodically.&lt;&#x2F;li&gt;
&lt;li&gt;Setup Opensearch and log to it.&lt;&#x2F;li&gt;
&lt;li&gt;Show logs and potentially a dashboard.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;introducing-tufte&quot;&gt;Introducing Tufte&lt;&#x2F;h1&gt;
&lt;p&gt;Taking from the project&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;taoensso&#x2F;tufte#tufte&quot;&gt;README&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tufte allows you to easily monitor the ongoing performance of your Clojure and ClojureScript applications in production and other environments.&lt;&#x2F;p&gt;
&lt;p&gt;It provides sensible application-level metrics, and gives them to you as Clojure data that can be easily analyzed programmatically.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I don&#x27;t think there is a better way to describe it so I will not try.&lt;&#x2F;p&gt;
&lt;p&gt;No, ok, I will try. In our context it can be seen as a tool to collect
metrics about our system, at runtime, with low overhead, and to work on
them (the metrics) as pure data to do, well, whatever we want to, for
example logging that data somewhere to be used later or directly fire
alarms if some part of the system is not behaving as expected.&lt;&#x2F;p&gt;
&lt;p&gt;This looks interesting in theory but how does it look in practice?&lt;&#x2F;p&gt;
&lt;p&gt;First of all we have to add tufte to our dependencies, as of today
the current version is &lt;code&gt;2.6.0&lt;&#x2F;code&gt;, so we can add the dependency like so:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:deps &lt;&#x2F;span&gt;&lt;span&gt;{com.taoensso&#x2F;tufte {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2.6.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can run a tiny experiment to get a taste of the data we can get;
first of all lets pretend we want to profile the good old FizzBuzz, for
which we have two implementations:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.fizzbuzz
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[clojure.core.match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[match]]))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizz-buzz-match
&lt;&#x2F;span&gt;&lt;span&gt;    [n]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;[(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;           [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt; _] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           [_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizz-buzz-cond
&lt;&#x2F;span&gt;&lt;span&gt;    [n]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[m3 (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;          m5 (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cond
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; m3 m5) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        m3 &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        m5 &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can profile it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.perf-log-es
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[taoensso.tufte &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; t]
&lt;&#x2F;span&gt;&lt;span&gt;              [codes.fpsd.fizzbuzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; fb]))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; Request to send `profile` stats to `println`:
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;add-basic-println-handler! &lt;&#x2F;span&gt;&lt;span&gt;{})
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profile
&lt;&#x2F;span&gt;&lt;span&gt;    {}
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dotimes &lt;&#x2F;span&gt;&lt;span&gt;[_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which will output something like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;pId                  nCalls        Min      50% ≤      90% ≤      95% ≤      99% ≤        Max       Mean   MAD      Clock  Total
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;:fizz-buzz-match         50     9.01μs     9.46μs    12.19μs    16.52μs   165.84μs   305.55μs    16.47μs  ±71%   823.61μs    31%
&lt;&#x2F;span&gt;&lt;span&gt;:fizz-buzz-cond          50     8.08μs     8.48μs    12.03μs    13.02μs    84.26μs   150.11μs    12.41μs  ±46%   620.52μs    23%
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Accounted                                                                                                          1.44ms    55%
&lt;&#x2F;span&gt;&lt;span&gt;Clock                                                                                                              2.65ms   100%
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Lets break down what we did and what we got:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We want to profile some code, in this case two FizzBuzz implementations&lt;&#x2F;li&gt;
&lt;li&gt;We have told tufte to print its results; we will see later other way to get the numbers&lt;&#x2F;li&gt;
&lt;li&gt;We have profiled the two functions&lt;&#x2F;li&gt;
&lt;li&gt;And finally we got our results in a nicely formatted table which gives us an idea of what we can get from tufte&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;One thing to notice is that tufte only profiles CPU time and does not take
into account memory usage; depending on our requirements it may be a deal
breaker for needs, in that case we can track memory usage on the side with
other tools or replace it completely.&lt;&#x2F;p&gt;
&lt;p&gt;Using the println handler may be enough when profiling something locally but
for something running in a server (or cluster) it may be more convenient to
log the metrics or apply some logic to collected metrics. For these use case
there are two options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;tufte&#x2F;profiled&lt;&#x2F;li&gt;
&lt;li&gt;tufte&#x2F;add-accumulating-handler!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;profiled&quot;&gt;profiled&lt;&#x2F;h2&gt;
&lt;p&gt;Like profile, it will run the enclosed forms but instead of directly returning
the result of the form and send the pstats to the configured handler, it returns
a tuple composed by:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the value of the form&lt;&#x2F;li&gt;
&lt;li&gt;a pstats object that can be inspected to collect the collected metrics&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To have a better understanding of the output of profiled we can run the following form:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;res &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profiled
&lt;&#x2F;span&gt;&lt;span&gt;               {}
&lt;&#x2F;span&gt;&lt;span&gt;             (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dotimes &lt;&#x2F;span&gt;&lt;span&gt;[_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;)))))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span&gt; res &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; =&amp;gt; nil
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;deref &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span&gt; res &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; =&amp;gt; continues...
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:clock &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:t0 30597219635504&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:t1 30597220674063&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:total 1038559&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:stats &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:min 3973&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mean 5973.8&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p75 4196.75&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mad-sum 165725.20000000007&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p99 47871.74999999987&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:n 50&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p25 4052.0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p90 4959.9000000000015&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:var 1.1782216812E8&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:max 81400&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mad 3314.5040000000013&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:loc &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:ns &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;codes.fpsd.perf-log-es&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:line 20&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:column 14&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:file &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;foca&#x2F;work&#x2F;playground&#x2F;src&#x2F;codes&#x2F;fpsd&#x2F;perf_log_es.clj&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:last 81400&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p50 4116.5&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:sum 298690&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p95 6182.649999999998&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:var-sum 5.891108406E9&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:min 3630&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mean 4549.58&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p75 3795.0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mad-sum 71821.36000000004&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p99 14535.559999999994&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:n 50&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p25 3707.0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p90 4196.1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:var 7170650.4836&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:max 15690&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mad 1436.427200000001&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:loc &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:ns &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;codes.fpsd.perf-log-es&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:line 22&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:column 14&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:file &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;foca&#x2F;work&#x2F;playground&#x2F;src&#x2F;codes&#x2F;fpsd&#x2F;perf_log_es.clj&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:last 15690&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p50 3735.5&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:sum 227479&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p95 12587.149999999998&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:var-sum 3.5853252418E8&lt;&#x2F;span&gt;&lt;span&gt;}}
&lt;&#x2F;span&gt;&lt;span&gt;   }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;WOW, Now we are speaking! Look at that, we have a &amp;quot;raw&amp;quot; representation of what the
println handler showed us but now we can work with it! For example we could directly
rise alerts depending on some collected metric or send the whole or a sample of that
data to a log collector. A recap of what we did and what we&#x27;ve got:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we have profiled a form with profiled instead of profile&lt;&#x2F;li&gt;
&lt;li&gt;we&#x27;ve got a tuple with the result of the form and the pstat object which contains&lt;&#x2F;li&gt;
&lt;li&gt;total amount of time of the enclosed forms&lt;&#x2F;li&gt;
&lt;li&gt;details of each p form, including percentile stas and the location of the code generating them&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Inspecting pstats directly as shown in the previous example may not be the most
convenient way to get value out of it, especially in a development environment; one
handy way of collecting this data while working on some piece of code and profiling it,
is to send pstats to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;djblue&#x2F;portal&quot;&gt;Portal&lt;&#x2F;a&gt; and use its powerful
views to explore the data generated by tufte, here is an example view:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;tufte-portal.png&quot; alt=&quot;portal show pstats&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There are multiple ways to get tufte&#x27;s pstats to portal but the simplest form
&amp;quot;to get there&amp;quot; is something like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;require &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;[portal.api &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; portal])
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;p &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;portal&#x2F;open&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;add-tap &lt;&#x2F;span&gt;&lt;span&gt;#&amp;#39;portal&#x2F;submit)
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[[res pstats] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profiled
&lt;&#x2F;span&gt;&lt;span&gt;                       {}
&lt;&#x2F;span&gt;&lt;span&gt;                       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dotimes &lt;&#x2F;span&gt;&lt;span&gt;[_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;                         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;                         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))))]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tap&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; res)
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tap&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;@pstats))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Portal&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;djblue&#x2F;portal&#x2F;0.46.0&#x2F;doc&#x2F;ui-concepts&quot;&gt;docs&lt;&#x2F;a&gt; have a section
to work with tufte available &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;djblue&#x2F;portal&#x2F;0.46.0&#x2F;doc&#x2F;guides&#x2F;tufte-profiling&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;add-accumulating-handler&quot;&gt;add-accumulating-handler!&lt;&#x2F;h2&gt;
&lt;p&gt;It is not always ideal, or needed to consume pstats objects on the fly,
even if it is cheap it will still adds up to your business logic runtime
or endpoints&#x27; response times. To avoid this we can accumulate pstats and
consume that on demand or periodically (for example every minute).
&lt;code&gt;tufte&#x2F;add-accumulating-handler!&lt;&#x2F;code&gt; will create a new pstats accumulator, returning it,
which can later be drained to get all collected stats to do, well, whatever we
want to, for example to log the accumulated stats somewhere. De-refing an accumulator
will return a map in the form of&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profile-id&lt;&#x2F;span&gt;&lt;span&gt; pstat}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Like the pstat object returned by &lt;code&gt;profiled&lt;&#x2F;code&gt;, to access its content we must &lt;code&gt;deref&lt;&#x2F;code&gt; it; here is a simple example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;accumulated-stats &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;add-accumulating-handler! &lt;&#x2F;span&gt;&lt;span&gt;{}))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profile
&lt;&#x2F;span&gt;&lt;span&gt;     {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:id :testing-1&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dotimes &lt;&#x2F;span&gt;&lt;span&gt;[_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10000&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profile
&lt;&#x2F;span&gt;&lt;span&gt;     {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:id :testing-2&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;     (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dotimes &lt;&#x2F;span&gt;&lt;span&gt;[_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-match &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;20000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz-cond &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;20000&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[[p-id pstats] @accumulated-stats]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tap&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;[p-id @pstats]))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And here is how it looks in Portal:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;2023-08-27_13-17-accumulated-pstats.png&quot; alt=&quot;accumulated pstats&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;2023-08-27_13-23-accumulated-pstats-details.png&quot; alt=&quot;accumulated pstats details&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;configuring-tufte&quot;&gt;Configuring Tufte&lt;&#x2F;h2&gt;
&lt;p&gt;It is worth spending few words on how we can configure Tufte to fine
tune it and to adapt it to our use case. We may have noticed that, profile,
profiled and add-accumulating-handler! accept a configuration map which,
excluding the last example, we left empty for simplicity. Here is a breakdown
of the available configuration options.&lt;&#x2F;p&gt;
&lt;p&gt;profile, profiled, p:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:level&lt;&#x2F;code&gt;: Optional profiling level, defaults to 5&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;:when&lt;&#x2F;code&gt;: Optional arbitrary conditional form that takes no arguments and return truty to enable profiling, falsy otherwise&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;:id&lt;&#x2F;code&gt;: Optional identifier provider to handlers (print or custom)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;:data&lt;&#x2F;code&gt;: Optional custom data to be provided to handlers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To be noted that the profiling level can be customized at runtime
by altering the var &lt;code&gt;tufte&#x2F;*min-level*&lt;&#x2F;code&gt;, the JVM property &lt;code&gt;taoensso.tufte.min-level&lt;&#x2F;code&gt;
or the environment variable &lt;code&gt;TAOENSSO_TUFTE_MIN_LEVEL&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Another configuration that will filter these forms is &lt;code&gt;tufte&#x2F;*ns-filter*&lt;&#x2F;code&gt; which can
customized by using &lt;code&gt;alter-var-root&lt;&#x2F;code&gt;, or JVM property &lt;code&gt;taoensso.tufte.ns-pattern&lt;&#x2F;code&gt; or
the environment variable &lt;code&gt;TAOENSSO_TUFTE_NS_PATTERN&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;add-accumulating-handler!&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:ns-pattern&lt;&#x2F;code&gt;: same as the global &lt;code&gt;tufte&#x2F;*ns-filter*&lt;&#x2F;code&gt; but as per handler basis, useful to have different handlers for ns patterns&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Refer to the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;taoensso&#x2F;tufte&#x2F;wiki&#x2F;1-Getting-started#conditional-profiling&quot;&gt;docs&lt;&#x2F;a&gt; to have the full picture about the configuring options.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparing-the-two-options&quot;&gt;Comparing the two options&lt;&#x2F;h2&gt;
&lt;p&gt;profiled will return pstats to be used right away but we pay the price for each call
to do something with the returned pstats, instead, add-accumulating-handler! will send
accumulated pstats to an handler that will be drained at convenient times, we can think
of it like an out of band process.
It is nice to be able to have both behaviors available so that we can fine tune our profiling
setup based on environments, namespace or custom conditions.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;small-section-about-collecting-data-and-log-it-with-mulog&quot;&gt;Small section about collecting data and log it with mulog&lt;&#x2F;h1&gt;
&lt;p&gt;We have briefly seen how we can profile our code with tufte and get some data about it,
we have also sent the data to portal which helps us to visualize it. This setup can be
handy while developing however, we are still far away from an actionable setup where we can
create or dashboards using the collected data. The missing pieces are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;something to send the collected data &amp;quot;somewhere&amp;quot;, for example mulog&lt;&#x2F;li&gt;
&lt;li&gt;a &amp;quot;somewhere&amp;quot; that can ingest this data so that we can work with it, for example opensearch&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;sending-pstats-to-a-log-collector&quot;&gt;Sending pstats to a log collector&lt;&#x2F;h1&gt;
&lt;p&gt;Next sections are about setting Opensearch and mulog as a way to send profiling data
&amp;quot;somewhere&amp;quot; else. If you are already familiar with these topics you can skip these
details (or maybe read it anyway and help me to fix mistakes ;) ).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;opensearch&quot;&gt;Opensearch&lt;&#x2F;h2&gt;
&lt;p&gt;To simplify (quite a lot) we can setup a local single node opensearch, available at port 9200,
with its nice dashboard, available at port 5601 accessible with admin:admin crendentials, using
docker compose and the following docker-compose.yml:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;services&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;os-node1&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;image&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;opensearchproject&#x2F;opensearch:2.3.0
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container_name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;os-node1
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;environment&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cluster.name=os-cluster
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;node.name=os-node1
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;discovery.type=single-node
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;bootstrap.memory_lock=true &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# along with the memlock settings below, disables swapping
&lt;&#x2F;span&gt;&lt;span&gt;          - &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# minimum and maximum Java heap size, recommend setting both to 50% of system RAM
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ulimits&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;memlock&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;soft&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;-1
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;hard&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;-1
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;nofile&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;soft&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;65536 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;hard&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;65536
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;volumes&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;os-data1:&#x2F;usr&#x2F;share&#x2F;opensearch&#x2F;data
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ports&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;9200:9200
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;9600:9600 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# required for Performance Analyzer
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;networks&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;os-net
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;os-dashboards&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;image&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;opensearchproject&#x2F;opensearch-dashboards:2.3.0
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container_name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;os-dashboards
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ports&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;5601:5601
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;expose&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;5601&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;environment&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OPENSEARCH_HOSTS&lt;&#x2F;span&gt;&lt;span&gt;: &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;[&amp;quot;https:&#x2F;&#x2F;os-node1:9200&amp;quot;]&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;networks&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;os-net
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;volumes&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;os-data1&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;networks&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;os-net&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we run this we can send our logs to &lt;code&gt;localhost:9200&lt;&#x2F;code&gt; and point the browser to &lt;code&gt;http:localhost:5601&lt;&#x2F;code&gt;
to see logs and setup our dashboards and alerts. One litle extra step is needed
to be able to send logs to this dockerized opensearch setup. Given that it uses a self signed
certificate we must instruct mulog to ignore certificate validation errors and also
we must provide our credentials (admin:admin), this is easily done by adding the
following map to mulog&#x27;s configuration:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; extra http options to be forwarded to the HTTP client
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http-opts &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:insecure? true
&lt;&#x2F;span&gt;&lt;span&gt;              &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;mulog uses the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dakrone&#x2F;clj-http&quot;&gt;clj-http&lt;&#x2F;a&gt; HTTP client library
so any setting provided in the :http-opts will be forwarded to that library when
making HTTP calls to send data to opensearch. These settings are neede because
the dockerized opensearch runs on HTTPS with a self signed cert (&lt;code&gt;:insecure?&lt;&#x2F;code&gt;)
and with basic auth credentials (&lt;code&gt;:basic-auth&lt;&#x2F;code&gt;); this is not a production grade
setup but is good enough to test locally.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mulog&quot;&gt;mulog&lt;&#x2F;h2&gt;
&lt;p&gt;mulog can help to send metrics to a collector and it has quite a lot of available publishers
that can send logs to different type of systems like ElasticSearch, Opensearch and many others
(see &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrunoBonacci&#x2F;mulog&#x2F;tree&#x2F;master&#x2F;doc&#x2F;publishers&quot;&gt;here&lt;&#x2F;a&gt; for a full list).&lt;&#x2F;p&gt;
&lt;p&gt;For the purpose of this article lets focus on Opensearch for which can use the ElasticSearch
publisher out of the box.&lt;&#x2F;p&gt;
&lt;p&gt;The bare minimum configuration needed to send events to Opensearch, considering our dockerized
environment, is the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;publisher &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:type :elasticsearch
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; Elasticsearch endpoint (REQUIRED)
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:url  &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;https:&#x2F;&#x2F;localhost:9200&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; extra http options to pass to the HTTP client
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http-opts &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:insecure? true
&lt;&#x2F;span&gt;&lt;span&gt;              &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span&gt;}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This configuration will be the argument of &lt;code&gt;com.brunobonacci.mulog&#x2F;start-publisher!&lt;&#x2F;code&gt;. To be 
able to use this publisher we must also add one more dependency &lt;code&gt;com.brunobonacci&#x2F;mulog-elasticsearch&lt;&#x2F;code&gt;
to our project.clj or deps.end.&lt;&#x2F;p&gt;
&lt;p&gt;We can start the publisher in our preferred way, manually or using an application
state management library like
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;integrant&quot;&gt;integrant&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;stuartsierra&#x2F;component&quot;&gt;component&lt;&#x2F;a&gt;,
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tolitius&#x2F;mount&quot;&gt;mount&lt;&#x2F;a&gt; and so on.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;&#x2F;h1&gt;
&lt;p&gt;So far we have seen:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;How to profile our code&lt;&#x2F;li&gt;
&lt;li&gt;How to get pstats in different ways, on demando or accumulating it&lt;&#x2F;li&gt;
&lt;li&gt;How to visualize that data&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Now, finally, we can put it all together in a tiny example web app that sends the pstats to
Opensearch. I&#x27;ll try to keep it as simple as possible, to capture the important details.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; web server setup with profiling middleware 
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;request-&amp;gt;endpoint-name
&lt;&#x2F;span&gt;&lt;span&gt;  [request]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; request &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:reitit.core&#x2F;match :data &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:request-method&lt;&#x2F;span&gt;&lt;span&gt; request)) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;profiler-middleware
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Middleware that wraps handlers with a call to t&#x2F;profile
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  using the endpoint name as the id of the &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  [wrapped]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[request]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;profile &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:id :ring-handler&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;p &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;or &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request-&amp;gt;endpoint-name&lt;&#x2F;span&gt;&lt;span&gt; request) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:unnamed-handler&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;wrapped&lt;&#x2F;span&gt;&lt;span&gt; request)))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizz-buzz-handler
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Ring handler that takes the number from the request path-params
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  and returns a JSON with the resulf of calling fizz-buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  [request]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;try
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[number (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; request
&lt;&#x2F;span&gt;&lt;span&gt;                     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:path-params
&lt;&#x2F;span&gt;&lt;span&gt;                     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:number
&lt;&#x2F;span&gt;&lt;span&gt;                     Integer&#x2F;parseInt)]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-handler :argument&lt;&#x2F;span&gt;&lt;span&gt; number)
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;json&#x2F;generate-string &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:result &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;[n (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;range &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inc&lt;&#x2F;span&gt;&lt;span&gt; number))]
&lt;&#x2F;span&gt;&lt;span&gt;                                              (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fb&#x2F;fizz-buzz&lt;&#x2F;span&gt;&lt;span&gt; n))})
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:content-type &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;catch&lt;&#x2F;span&gt;&lt;span&gt; NumberFormatException e
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:fizz-buzz-handler :error &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt; e))
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 400
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;json&#x2F;generate-string &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:error &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Invalid numeric parameter &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; e)})
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:content-type &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create-app
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a ring handler that will route &#x2F;events to the SSE handler
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;   and that will servr  static content form project&amp;#39;s resource&#x2F;public directory&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;ring-handler
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;router
&lt;&#x2F;span&gt;&lt;span&gt;    [[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;fizz-buzz&#x2F;:number&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:get &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; fizz-buzz-handler
&lt;&#x2F;span&gt;&lt;span&gt;                                  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name ::fizz-buzz&lt;&#x2F;span&gt;&lt;span&gt;}}]
&lt;&#x2F;span&gt;&lt;span&gt;     ]
&lt;&#x2F;span&gt;&lt;span&gt;     
&lt;&#x2F;span&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:data &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:middleware &lt;&#x2F;span&gt;&lt;span&gt;[profiler-middleware
&lt;&#x2F;span&gt;&lt;span&gt;                         params&#x2F;wrap-params]}})
&lt;&#x2F;span&gt;&lt;span&gt;   ))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; define all integrant components
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;init-key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;accumulator &lt;&#x2F;span&gt;&lt;span&gt;[_ config]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;t&#x2F;add-accumulating-handler! &lt;&#x2F;span&gt;&lt;span&gt;{}))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;init-key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;publisher &lt;&#x2F;span&gt;&lt;span&gt;[_ config]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;start-publisher!&lt;&#x2F;span&gt;&lt;span&gt; config))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;init-key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;drain &lt;&#x2F;span&gt;&lt;span&gt;[_ {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[delay accumulator]}]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;future
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;while &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Thread&#x2F;sleep&lt;&#x2F;span&gt;&lt;span&gt; delay)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; drain pstats if any
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;when-let &lt;&#x2F;span&gt;&lt;span&gt;[stats @accumulator]
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[[p-id pstats] stats]
&lt;&#x2F;span&gt;&lt;span&gt;          (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[[handler handler-stats] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:stats &lt;&#x2F;span&gt;&lt;span&gt;@pstats)]
&lt;&#x2F;span&gt;&lt;span&gt;            (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keys &lt;&#x2F;span&gt;&lt;span&gt;[loc min max mad p95 p99]} handler-stats]
&lt;&#x2F;span&gt;&lt;span&gt;              (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:accumulator-drain :perf-id&lt;&#x2F;span&gt;&lt;span&gt; p-id &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; handler)
&lt;&#x2F;span&gt;&lt;span&gt;              (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log&lt;&#x2F;span&gt;&lt;span&gt; p-id &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; handler &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:loc&lt;&#x2F;span&gt;&lt;span&gt; loc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:min&lt;&#x2F;span&gt;&lt;span&gt; min &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:max&lt;&#x2F;span&gt;&lt;span&gt; max &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mad&lt;&#x2F;span&gt;&lt;span&gt; mad &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p95&lt;&#x2F;span&gt;&lt;span&gt; p95 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:p99&lt;&#x2F;span&gt;&lt;span&gt; p99))))))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:accumulator-drain-finished&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;            
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;halt-key! &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;drain &lt;&#x2F;span&gt;&lt;span&gt;[_ pstat-future]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;future-cancel&lt;&#x2F;span&gt;&lt;span&gt; pstat-future))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;init-key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http&#x2F;server &lt;&#x2F;span&gt;&lt;span&gt;[_ config]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http&#x2F;start-server &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;create-app&lt;&#x2F;span&gt;&lt;span&gt;) config))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defmethod &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ig&#x2F;halt-key! &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http&#x2F;server &lt;&#x2F;span&gt;&lt;span&gt;[_ server]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.close&lt;&#x2F;span&gt;&lt;span&gt; server))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; and the configuration for all components of the system
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; being basically all &amp;quot;raw&amp;quot; data it can from an external
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; source like an edn file
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;config &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;accumulator &lt;&#x2F;span&gt;&lt;span&gt;{}
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;drain &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:delay 5000
&lt;&#x2F;span&gt;&lt;span&gt;                              &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:accumulator &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ig&#x2F;ref &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:profiler&#x2F;accumulator&lt;&#x2F;span&gt;&lt;span&gt;)}
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;publisher &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:type :elasticsearch
&lt;&#x2F;span&gt;&lt;span&gt;                               &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; Elasticsearch endpoint (REQUIRED)
&lt;&#x2F;span&gt;&lt;span&gt;                               &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:url  &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;https:&#x2F;&#x2F;localhost:9200&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                               &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; extra http options to pass to the HTTP client
&lt;&#x2F;span&gt;&lt;span&gt;                               &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http-opts &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:insecure? true
&lt;&#x2F;span&gt;&lt;span&gt;                                           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}
&lt;&#x2F;span&gt;&lt;span&gt;             
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:http&#x2F;server &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 8080
&lt;&#x2F;span&gt;&lt;span&gt;                           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join? false&lt;&#x2F;span&gt;&lt;span&gt;}})
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; prepare the system, it will be intilialized only after
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; de-referencing the system var
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;system
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;delay &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ig&#x2F;init&lt;&#x2F;span&gt;&lt;span&gt; config)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It is quite a lot of code! Lets break it down:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We have setup a small web app with one endpoint that will return a list of the application of &lt;code&gt;fizz-buzz&lt;&#x2F;code&gt; to numbers from 1 to &lt;code&gt;:number&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;We have defined a middlware &lt;code&gt;profiler-middleware&lt;&#x2F;code&gt; that will wrap any handler and will profile it&lt;&#x2F;li&gt;
&lt;li&gt;We spawn a thread that that, at defined times, will drain the stats accumulator and will log some stats using &lt;code&gt;mulog&lt;&#x2F;code&gt; targetting &lt;code&gt;opensearch&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Everything is setup using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;integrant&quot;&gt;integrant&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If we start the system we can test our new shiny API usung &lt;code&gt;curl&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span&gt; curl http:&#x2F;&#x2F;localhost:8080&#x2F;fizz-buzz&#x2F;foo
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Invalid numeric parameter java.lang.NumberFormatException: For input string: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;foo&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span&gt; curl http:&#x2F;&#x2F;localhost:8080&#x2F;fizz-buzz&#x2F;10
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;result&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;:[1,2,&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;,4,&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;,7,8,&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After poking the endponits for a bit we should accumulated some stats and we should be able to 
look at opensearch dashboard to inspect logs and potentially create views and alerts, here is an example:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;2023-09-04_07-54-opensearch-accumulator.png&quot; alt=&quot;opensearch&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The code supporting this post can be found &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;playground&#x2F;-&#x2F;blob&#x2F;main&#x2F;src&#x2F;codes&#x2F;fpsd&#x2F;perf_log_es.clj&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;In this post we have seen how we can collect informations about our running system
and send them to a convenient place for subsequent analysis, to setup alerts or 
created beautiful visualizations. I did skip soe details, for example how to
configure integrant, mulog or opensearch and I suggest to refer to their own documentation.&lt;&#x2F;p&gt;
&lt;p&gt;As a side note I think this post was a bit too big for a Clojure Bite issue, touching too
many arguments without focusing on one topic specifically. But I was already too late
when I&#x27;ve discovered this problem so the post it is what it is :D&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;169kg71&#x2F;clojure_bites_profiling_with_tufte&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1698586879099088911?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstodon.org&#x2F;@fpsd&#x2F;111005511712075052&quot;&gt;Mastodon&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_overview-activity-7104519819374305280-Aq-9?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;updates&quot;&gt;Updates&lt;&#x2F;h1&gt;
&lt;p&gt;From comments in Reddit I&#x27;ve learnead about alternatives for profiling and tracing&lt;&#x2F;p&gt;
&lt;h2 id=&quot;clj-async-profiler-comment&quot;&gt;clj-async-profiler (&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;169kg71&#x2F;comment&#x2F;jz2qrxd&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;comment&lt;&#x2F;a&gt;)&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure-goes-fast&#x2F;clj-async-profiler&quot;&gt;clj-async-profiler&lt;&#x2F;a&gt;, a much more
accurate tracing profiler; probably best suited when developing and not in production, but
still one more good tool to keep in mind.&lt;&#x2F;p&gt;
&lt;p&gt;Edit:&lt;&#x2F;p&gt;
&lt;p&gt;Looking at the README it looks like it is suitable for production environments too:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;During profiling, clj-async-profiler has very low overhead, so it is suitable for usage even in highly loaded production scenarios.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;mulog-comment&quot;&gt;mulog (&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;169kg71&#x2F;comment&#x2F;jz32dei&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;comment&lt;&#x2F;a&gt;)&lt;&#x2F;h2&gt;
&lt;p&gt;mulog itself can create traces (using u&#x2F;trace) for more fine grained metrics;
the tradoff is that the number of events to be recorded can be very high and
tufte&#x27;s accumulated stats can mitigate this problem. Both tufte and mulog support
conditional profiling&#x2F;logging (for example via sampling) which can help to keep
the number of events under control.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - SSE with Aleph and Reitit</title>
          <pubDate>Thu, 03 Aug 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-bites-sse/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-sse/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-sse/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;To coordinate the refinement and estimation sessions in Unrefined
I wanted to have a way to give users instant feedback on some events
like people estimating or moving to the next ticket. At the same time
I wanted to keep the frontend code as simple as possible i.e. I tried
to avoid writing a SPA as much as possible so I settled to the following
setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;plain HTML GET for server side rendering of a view&lt;&#x2F;li&gt;
&lt;li&gt;plain HTML POST for &amp;quot;actions&amp;quot; and server side rendering of the result&lt;&#x2F;li&gt;
&lt;li&gt;Server Sent Events (&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Server-sent_events&quot;&gt;SSE&lt;&#x2F;a&gt;) for realtime push updates&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This setup worked pretty well, it is easy to maintain and requires not
much JavaScript code given the small size of this webapp.&lt;&#x2F;p&gt;
&lt;p&gt;In this post I&#x27;ll describe the basic building blocks to replicate such
setup using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;aleph&quot;&gt;Aleph&lt;&#x2F;a&gt; which provides a nice implementation of SSE
(and Websockets, but not covered here) leveraging &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;manifold&#x2F;&quot;&gt;Manifold&lt;&#x2F;a&gt;; the same
result can be achieved using other web servers and core.async but I haven&#x27;t tried it yet.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;sse-overview&quot;&gt;SSE overview&lt;&#x2F;h1&gt;
&lt;p&gt;Before jumping into the implementation it is better to spend few words on the
SSE spec. If you are already familiar with it you can jump to the next section,
or you can stay here and help me to spot and fix possible mistakes ;) .&lt;&#x2F;p&gt;
&lt;p&gt;SSE provides a simple (but effective) way to stream events from a server to
subscribed clients. Clients create a persistent connection to a server and
wait for events being sent to them.&lt;&#x2F;p&gt;
&lt;p&gt;The client connects to an endpoint using the &lt;a href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;API&#x2F;EventSource&quot;&gt;EventSource&lt;&#x2F;a&gt; interface and the server
is expected to reply with `text&#x2F;event-stream` MIME type, creating effectively
a persistent connection through which the server can send new events. See &lt;a href=&quot;https:&#x2F;&#x2F;html.spec.whatwg.org&#x2F;multipage&#x2F;server-sent-events.html#server-sent-events&quot;&gt;here&lt;&#x2F;a&gt;
for more details about the event format. As a quick introduction, events can be
just data:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;data: event payload
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;data: event payload with
&lt;&#x2F;span&gt;&lt;span&gt;data: multiple lines
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or event types can be included in the event itself:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;event: your-event-type
&lt;&#x2F;span&gt;&lt;span&gt;deta: your event payload
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To recap:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;events are delimited by an empty line, keep this in mind when serializing your events&lt;&#x2F;li&gt;
&lt;li&gt;events can have a type (or tag, however you want to call it) which can be handy when subscribing to specific events on the client side&lt;&#x2F;li&gt;
&lt;li&gt;on first connection the endpoint MUST reply with the &lt;code&gt;text&#x2F;event-stream&lt;&#x2F;code&gt; MIME type&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For more detailed information please refer to these links:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;API&#x2F;Server-sent_events&quot;&gt;MDN&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;html.spec.whatwg.org&#x2F;multipage&#x2F;server-sent-events.html&quot;&gt;Spec&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.w3schools.com&#x2F;html&#x2F;html5server_sentevents.asp&quot;&gt;W3C&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There is a ton of material out there about SSE, these links are just quick references.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;project-setup&quot;&gt;Project setup&lt;&#x2F;h1&gt;
&lt;p&gt;Time to get some code! Lets start with a simple base setup for the server side.&lt;&#x2F;p&gt;
&lt;p&gt;We are going to create a web app with a single endpoint that will stream events
back to a client to get things started, later we will add a router to handle two
endpoints, one to send data and another to receive events and a tiny JS implementation
of the EventSource interface to render events to a page.&lt;&#x2F;p&gt;
&lt;p&gt;We can start by putting down some deps, I am using &lt;code&gt;tool.deps&lt;&#x2F;code&gt; but it should be easy
to port them to lain or boot if needed:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:deps &lt;&#x2F;span&gt;&lt;span&gt;{org.clojure&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.11.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            ring&#x2F;ring-core {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.10.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            aleph&#x2F;aleph {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.6.3&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can start with an HTTP handler, not the fanciest code you&#x27;ll ever see in your
life but good enough to get started&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.sse
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[aleph.http &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; http]))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler &lt;&#x2F;span&gt;&lt;span&gt;[_req]
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;event-stream&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data: one event, k thanks bye!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;comment
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http&#x2F;start-server&lt;&#x2F;span&gt;&lt;span&gt; handler {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 8080
&lt;&#x2F;span&gt;&lt;span&gt;                                  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join? false&lt;&#x2F;span&gt;&lt;span&gt;})
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After eveluating this code and the inner comment section we will have a running
web server that replies to every route with a &lt;code&gt;text&#x2F;event-source&lt;&#x2F;code&gt; body and will
close the connection afterwards; not too different compared to a &amp;quot;normal&amp;quot; HTTP
endpoint but we will get there. Here is the output of curl session:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; curl localhost:8080&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -vv
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Trying 127.0.0.1:8080&amp;amp;#x2026;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Connected to localhost (127.0.0.1) port 8080 (#0)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; GET &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt; HTTP&#x2F;1.1
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Host: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;localhost:8080
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; User-Agent: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;curl&#x2F;7.81.0
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Accept: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;**&#x2F;**
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Mark bundle as not supporting multiuse
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; HTTP&#x2F;1.1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt; OK
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Content-Type: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&#x2F;event-stream
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Server: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Aleph&#x2F;0.6.3
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Date: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Sun,&lt;&#x2F;span&gt;&lt;span&gt; 30 Jul 2023 19:13:32 GMT
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Connection: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Keep-Alive
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; content-length: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;31
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; one event, k thanks bye!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Connection #0 to host localhost left intact
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;streaming-data&quot;&gt;Streaming data&lt;&#x2F;h1&gt;
&lt;p&gt;Aleph builds its functionalities on top of Manifold which provides asynchronous
data structures like &lt;code&gt;manifold.deferred&#x2F;deferred&lt;&#x2F;code&gt; or &lt;code&gt;manifold.stream&#x2F;stream&lt;&#x2F;code&gt;
(and more), these data structure can be used as the body of a response and
Aleph will return their contents as soon as they are available and will close
the connection as soon as these sources will be closed. Manifold has been released
almost at the same time of &lt;code&gt;core.async&lt;&#x2F;code&gt; (&lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;guides&#x2F;async_walkthrough&quot;&gt;docs&lt;&#x2F;a&gt;) and can be also used to work with async channels, which
can be handy if we have some part of our system that relies on them. Citing its
own &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;manifold#readme&quot;&gt;Readme&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Manifold provides basic building blocks for asynchronous programming, and can be used as a translation layer between libraries which use similar, but incompatible, abstractions.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Manifold provides two core abstractions: deferreds, which represent a single asynchronous value, and streams, which represent an ordered sequence of asynchronous values.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Manifold API has a lot to offer and I encourage everyone to get to its docs
to get a better understanding of this library, to approach it quickly it could
be enough to see how it is used in Aleph&#x27;s examples &lt;a href=&quot;https:&#x2F;&#x2F;aleph.io&#x2F;aleph&#x2F;literate.html#aleph.examples.http&quot;&gt;here&lt;&#x2F;a&gt;.
Other, more up to date examples are available &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;aleph&#x2F;tree&#x2F;master&#x2F;examples&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For the purpose of this exercise we can think of Manifold streams as channels to
which we can put data and from which Aleph read data to send back events to an
EventSource client.&lt;&#x2F;p&gt;
&lt;p&gt;Lets re-write the handler in order to produce periodic events that later will be
consumed by a client&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.sse
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[aleph.http &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; http]
&lt;&#x2F;span&gt;&lt;span&gt;                [manifold.stream &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; s]))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;format-event &lt;&#x2F;span&gt;&lt;span&gt;[body]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data: &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; body &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n\n&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler &lt;&#x2F;span&gt;&lt;span&gt;[_req]
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;event-stream&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[counter (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;s&#x2F;periodically
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1000
&lt;&#x2F;span&gt;&lt;span&gt;                #(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format-event &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Sending event #&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; counter inc)))))})
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This new endpoint will send an event every second, until the client closes the
connection; &lt;code&gt;(manifold.stream&#x2F;periodically delay-ms fn)&lt;&#x2F;code&gt; returns a stream to
which a new value is sent by calling &lt;code&gt;fn&lt;&#x2F;code&gt; after &lt;code&gt;delay-ms&lt;&#x2F;code&gt; milliseconds.
The &lt;code&gt;format-event&lt;&#x2F;code&gt; fn is a helper that will return a SSE event, with only the
&lt;code&gt;data&lt;&#x2F;code&gt; part, given any text renderable &lt;code&gt;body&lt;&#x2F;code&gt;; it could be improved to properly
handle data bodies which include new lines generating multiple &lt;code&gt;data:&lt;&#x2F;code&gt; section,
but I guess you can do that easily if you want to ;) .&lt;&#x2F;p&gt;
&lt;p&gt;To recap the new endpoint will send back an event with a progressive number,
starting from 1, to every client that connects to the web server.&lt;&#x2F;p&gt;
&lt;p&gt;This what we get if we try the endpoint with curl:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; curl localhost:8080&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -vv
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Trying 127.0.0.1:8080&amp;amp;#x2026;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Connected to localhost (127.0.0.1) port 8080 (#0)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; GET &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt; HTTP&#x2F;1.1
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Host: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;localhost:8080
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; User-Agent: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;curl&#x2F;7.81.0
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Accept: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;**&#x2F;**
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Mark bundle as not supporting multiuse
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; HTTP&#x2F;1.1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt; OK
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Content-Type: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&#x2F;event-stream
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Server: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Aleph&#x2F;0.6.3
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Date: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Mon,&lt;&#x2F;span&gt;&lt;span&gt; 31 Jul 2023 17:37:15 GMT
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Connection: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Keep-Alive
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; transfer-encoding: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;chunked
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#2
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#3
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#4
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Oooook, finally something interesting is happening! A client connects to
an endpoint that is streaming back events at regular intervals; if we stop
and think about it we have laid out the foundation to stream events to a
client, by leveraging &lt;code&gt;manifold.stream&#x2F;stream&lt;&#x2F;code&gt; abstraction and Aleph&#x27;s
ability to use it to send back responses to the caller.&lt;&#x2F;p&gt;
&lt;p&gt;I think that implementing a browser client would be even more exciting, so
lets do that! Actually before moving to a browser client I&#x27;d like to improve
to server a bit by:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;adding a router&lt;&#x2F;li&gt;
&lt;li&gt;that will return an HTML page with all the frontend code&lt;&#x2F;li&gt;
&lt;li&gt;and exposes the SSE handler that we have implemented so far&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;adding-the-router&quot;&gt;Adding the router&lt;&#x2F;h1&gt;
&lt;p&gt;There are many routers available today, lately I am enjoying using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;metosin&#x2F;reitit&quot;&gt;reitit&lt;&#x2F;a&gt;
which I find very intuitive and comes with a comprehensive documentation;
other good alternatives can be &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;compojure&quot;&gt;compojure&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;juxt&#x2F;bidi&quot;&gt;bidi&lt;&#x2F;a&gt; (and many &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;juxt&#x2F;bidi#comparison-with-other-routing-libraries&quot;&gt;more&lt;&#x2F;a&gt;) or even manual
routing of requests, anyway as you can imagine we will setup out routes with
reitit so we can start by adding it to our deps:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    metosin&#x2F;reitit {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.7.0-alpha5&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(yes it is an alpha version, I am confident that a stable 0.7.x will be out soon)&lt;&#x2F;p&gt;
&lt;p&gt;and setup the router to serve the static page holding the JS code and one
endpoint to handle the SSE events:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.sse
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[aleph.http &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; http]
&lt;&#x2F;span&gt;&lt;span&gt;                [manifold.stream &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; s]
&lt;&#x2F;span&gt;&lt;span&gt;                [reitit.ring &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; ring]))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;format-event
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a properly formatted event payload&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      [body]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data: &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; body &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n\n&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;sse-events &lt;&#x2F;span&gt;&lt;span&gt;[_req]
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;event-stream&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[counter (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;s&#x2F;periodically
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1000
&lt;&#x2F;span&gt;&lt;span&gt;                #(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format-event &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Sending event #&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; counter inc)))))})
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create-app
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a ring handler that will route &#x2F;events to the SSE handler
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;       and that will servr  static content form project&amp;#39;s resource&#x2F;public directory&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      []
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;ring-handler
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;router
&lt;&#x2F;span&gt;&lt;span&gt;        [[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:get &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; sse-events
&lt;&#x2F;span&gt;&lt;span&gt;                           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name ::events&lt;&#x2F;span&gt;&lt;span&gt;}}]]
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;routes
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;create-resource-handler &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:path &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;create-default-handler&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; Web server maangement code to make it easy to start and stop a server
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; after changesto router or handlers
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;server_ &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;nil&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;start-server! &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;reset!&lt;&#x2F;span&gt;&lt;span&gt; server_ (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http&#x2F;start-server &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;create-app&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                                         {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 8080
&lt;&#x2F;span&gt;&lt;span&gt;                                          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join? false&lt;&#x2F;span&gt;&lt;span&gt;})))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;stop-server! &lt;&#x2F;span&gt;&lt;span&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; server_ (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[s]
&lt;&#x2F;span&gt;&lt;span&gt;                       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;when&lt;&#x2F;span&gt;&lt;span&gt; s
&lt;&#x2F;span&gt;&lt;span&gt;                         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.close&lt;&#x2F;span&gt;&lt;span&gt; s)))))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;comment
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;start-server!&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;stop-server!&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Please change the namespace to reflect your project setup)&lt;&#x2F;p&gt;
&lt;p&gt;To recap:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we have an helper function to format SSE events&lt;&#x2F;li&gt;
&lt;li&gt;we have renamed the generic &lt;code&gt;handler&lt;&#x2F;code&gt; to &lt;code&gt;sse-events&lt;&#x2F;code&gt; but it is the same as before&lt;&#x2F;li&gt;
&lt;li&gt;we have created a ring router to serve GETs to &lt;code&gt;&#x2F;events&lt;&#x2F;code&gt; and the static content&lt;&#x2F;li&gt;
&lt;li&gt;we have added some helper functions to start and stop the server&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If we evaluate the buffer and start the server we can verify that the &lt;code&gt;&#x2F;events&lt;&#x2F;code&gt; endpoint
is working as before:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span&gt; curl localhost:8080&#x2F;events&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -vv
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Trying 127.0.0.1:8080&amp;amp;#x2026;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Connected to localhost (127.0.0.1) port 8080 (#0)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; GET &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;events&lt;&#x2F;span&gt;&lt;span&gt; HTTP&#x2F;1.1
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Host: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;localhost:8080
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; User-Agent: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;curl&#x2F;7.81.0
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Accept: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;**&#x2F;**
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Mark bundle as not supporting multiuse
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; HTTP&#x2F;1.1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt; OK
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Content-Type: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&#x2F;event-stream
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Server: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Aleph&#x2F;0.6.3
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Date: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Wed,&lt;&#x2F;span&gt;&lt;span&gt; 02 Aug 2023 06:29:16 GMT
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; Connection: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Keep-Alive
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; transfer-encoding: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;chunked
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data:&lt;&#x2F;span&gt;&lt;span&gt; Sending event &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#2
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To recap:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we have added a router (docs &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;metosin&#x2F;reitit&#x2F;0.7.0-alpha5&#x2F;doc&#x2F;ring&#x2F;ring-router&quot;&gt;here&lt;&#x2F;a&gt;) for the &lt;code&gt;&#x2F;events&lt;&#x2F;code&gt; endpoint&lt;&#x2F;li&gt;
&lt;li&gt;we have added a way to serve static files from the jar resources (docs &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;metosin&#x2F;reitit&#x2F;0.7.0-alpha5&#x2F;doc&#x2F;ring&#x2F;static-resources&quot;&gt;here&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;a way to conveniently start&#x2F;stop the webapp&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I have decided to avoid some details and provide a pre-cooked solution because
reitit&#x27;s docs are quite comprehensive, and to focus on SSE.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;frontend&quot;&gt;Frontend&lt;&#x2F;h1&gt;
&lt;p&gt;In the previous step we have added a way to serve static files, now we can add a
basic web page where to render events as we get them. First of all lets see if we
can correctly serve a simple HTML file; paste the following content in &lt;code&gt;resources&#x2F;public&#x2F;index.html&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        Your events here!
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now start the server and point your browser to &lt;a href=&quot;http:&#x2F;&#x2F;localhost:8080&quot;&gt;http:&#x2F;&#x2F;localhost:8080&lt;&#x2F;a&gt;, you should
something like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;2023-08-02_19-24-SSE-initial-page.png&quot; alt=&quot;initial-page&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Quite boring still…we can try subscribing to the &lt;code&gt;&#x2F;events&lt;&#x2F;code&gt; endpoint and render
the upcoming events! By coincidence we already have a &lt;code&gt;div&lt;&#x2F;code&gt; with the id &lt;code&gt;events&lt;&#x2F;code&gt;
that we can use for this purpose ;)&lt;&#x2F;p&gt;
&lt;p&gt;What we need to do is:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;instantiate an &lt;code&gt;EventSource&lt;&#x2F;code&gt; object that connects to the &lt;code&gt;events&lt;&#x2F;code&gt; endpoint&lt;&#x2F;li&gt;
&lt;li&gt;subscribe to incoming events&lt;&#x2F;li&gt;
&lt;li&gt;render event data inside the &lt;code&gt;events&lt;&#x2F;code&gt; div&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For simplicity we will embedd the JS code inside the &lt;code&gt;index.html&lt;&#x2F;code&gt; page:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        Your events here!
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;javascript&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;source &lt;&#x2F;span&gt;&lt;span&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;EventSource&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;source&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;onmessage &lt;&#x2F;span&gt;&lt;span&gt;= (e) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container &lt;&#x2F;span&gt;&lt;span&gt;= document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prepend&lt;&#x2F;span&gt;&lt;span&gt;(`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Received event with data: &lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;.data}`)
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we load &lt;a href=&quot;http:localhost:8080&quot;&gt;http:localhost:8080&lt;&#x2F;a&gt; again we should see something like the following
screenshot, again nothing super exciting but I hope you can feel the potential
of this approach!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;2023-08-02_19-49-SSE-getting-events.png&quot; alt=&quot;getting-events&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Somehow I suspect that this is not enough yet, maybe we can spice it up with a
super simplified chat room?&lt;&#x2F;p&gt;
&lt;h1 id=&quot;generating-and-consuming-events&quot;&gt;Generating and consuming events&lt;&#x2F;h1&gt;
&lt;p&gt;What do you need to do to implement the simplest chat room ever? Our MVP
should at least provide:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;an event bus to broadcast the messages to all connected users&lt;&#x2F;li&gt;
&lt;li&gt;an endpoint to consume those messages and send to the event bus&lt;&#x2F;li&gt;
&lt;li&gt;a form where to write our messages&lt;&#x2F;li&gt;
&lt;li&gt;a client that can consume such events&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Following the previous bullet points I&#x27;ll start with the event bus.
Manifold provides a convenient implementation of an even bus
available at &lt;code&gt;manifold.bus&#x2F;event-bus&lt;&#x2F;code&gt;. This provides a way to subscribe
a consumer to a topic (or SSE endpoint) and send messages to a topic
which will be sent to all connected consumers. For simplicity
we will have one single topic &lt;code&gt;the-chat-room&lt;&#x2F;code&gt; but it would easy to
extend this example to support multiple rooms.&lt;&#x2F;p&gt;
&lt;p&gt;Second thing to do is to add an endpoint that will get the chat message
and will push it to the event bus, lets see some code:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; codes.fpsd.sse
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[aleph.http &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; http]
&lt;&#x2F;span&gt;&lt;span&gt;                [manifold.bus &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; b]
&lt;&#x2F;span&gt;&lt;span&gt;                [reitit.ring &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; ring]
&lt;&#x2F;span&gt;&lt;span&gt;                [ring.middleware.params &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; params]))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; the one and only event bus needed for this app
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; using defonce to be able to possibly evaluate the
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; full buffer without breaking existing connections
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defonce &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;event-bus &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;b&#x2F;event-bus&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;format-event
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a properly formatted event payload&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      [body]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data: &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; body &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n\n&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;sse-events &lt;&#x2F;span&gt;&lt;span&gt;[_req]
&lt;&#x2F;span&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;event-stream&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;b&#x2F;subscribe&lt;&#x2F;span&gt;&lt;span&gt; event-bus &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;the-chat-room&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)})
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;send-message! &lt;&#x2F;span&gt;&lt;span&gt;[request]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[message (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format-event &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; request &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:params &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)))]
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;b&#x2F;publish!&lt;&#x2F;span&gt;&lt;span&gt; event-bus &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;the-chat-room&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; message)
&lt;&#x2F;span&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 204
&lt;&#x2F;span&gt;&lt;span&gt;         &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:content-type &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;plain&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;         &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;quot;}))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create-app
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a ring handler that will route &#x2F;events to the SSE handler
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;       and that will servr  static content form project&amp;#39;s resource&#x2F;public directory&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      []
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;ring-handler
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;router
&lt;&#x2F;span&gt;&lt;span&gt;        [[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:get &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; sse-events
&lt;&#x2F;span&gt;&lt;span&gt;                           &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name ::events&lt;&#x2F;span&gt;&lt;span&gt;}}]
&lt;&#x2F;span&gt;&lt;span&gt;         [&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;send-message&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:post &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:name ::send-message
&lt;&#x2F;span&gt;&lt;span&gt;                                  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler&lt;&#x2F;span&gt;&lt;span&gt; send-message!}}]]
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:data &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:middleware &lt;&#x2F;span&gt;&lt;span&gt;[params&#x2F;wrap-params]}})
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;routes
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;create-resource-handler &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:path &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ring&#x2F;create-default-handler&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;       ))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here is a breakdown of the changes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we requiring &lt;code&gt;manifold.bus&lt;&#x2F;code&gt; to have access to &lt;code&gt;b&#x2F;event-bus&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;we instantiate an event bus&lt;&#x2F;li&gt;
&lt;li&gt;the handler &lt;code&gt;sse-events&lt;&#x2F;code&gt; will subscribe users to the chat topic and will send back events&lt;&#x2F;li&gt;
&lt;li&gt;the handler &lt;code&gt;send-message&lt;&#x2F;code&gt; will take the &lt;code&gt;message&lt;&#x2F;code&gt; from the request body and send it to the event bus&lt;&#x2F;li&gt;
&lt;li&gt;we have added a new route &lt;code&gt;send-message&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;we have added a middleware that will parse the request body to the key &lt;code&gt;:params&lt;&#x2F;code&gt; of the &lt;code&gt;request&lt;&#x2F;code&gt; map&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Quite straight forward, isn&#x27;t it?&lt;&#x2F;p&gt;
&lt;p&gt;Few pointers if you want to dig deeper in the APIs show here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;metosin&#x2F;reitit&#x2F;0.7.0-alpha5&#x2F;doc&#x2F;ring&#x2F;data-driven-middleware&quot;&gt;Data driven middlewares&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clj-commons&#x2F;manifold&#x2F;blob&#x2F;master&#x2F;doc&#x2F;stream.md&quot;&gt;Manifold&#x27;s streams and event bus&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Now we can implement the frontend:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;          &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;form &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;onsubmit&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;event&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;preventDefault&lt;&#x2F;span&gt;&lt;span&gt;(); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sendMessage&lt;&#x2F;span&gt;&lt;span&gt;()&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;placeholder&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Your message here&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&#x2F;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;submit&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Send&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&#x2F;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;          &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Past messages&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;first load&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;javascript&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sendMessage &lt;&#x2F;span&gt;&lt;span&gt;= () &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input &lt;&#x2F;span&gt;&lt;span&gt;= document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;send-message&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;,
&lt;&#x2F;span&gt;&lt;span&gt;            {method: &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;,
&lt;&#x2F;span&gt;&lt;span&gt;             headers: {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;application&#x2F;x-www-form-urlencoded&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;},
&lt;&#x2F;span&gt;&lt;span&gt;             body: `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;message=&lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span&gt;.value}`}).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;then&lt;&#x2F;span&gt;&lt;span&gt;(() &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span&gt;.value = &amp;#39;&amp;#39;})
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;false
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;appendMessage &lt;&#x2F;span&gt;&lt;span&gt;= (text) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p &lt;&#x2F;span&gt;&lt;span&gt;= document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;createElement&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;appendChild&lt;&#x2F;span&gt;&lt;span&gt;(document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;createTextNode&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container &lt;&#x2F;span&gt;&lt;span&gt;= document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prepend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;source &lt;&#x2F;span&gt;&lt;span&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;EventSource&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;events&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;)
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;source&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;onmessage &lt;&#x2F;span&gt;&lt;span&gt;= (e) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;appendMessage&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;.data)
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Yeah the code looks early 2000 era of HTML+JS (if we exclude the use of &lt;code&gt;fetch&lt;&#x2F;code&gt;),
but I think it shows the core concepts nicely:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we now have a form where we can write our message&lt;&#x2F;li&gt;
&lt;li&gt;we prepend messages to the &lt;code&gt;events&lt;&#x2F;code&gt; div as soon as we will receive them&lt;&#x2F;li&gt;
&lt;li&gt;we use &lt;code&gt;fetch&lt;&#x2F;code&gt; API to POST messages to the &lt;code&gt;send-message&lt;&#x2F;code&gt; endpoint (wow a SPA without React!)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Again sorry for the JS code quality but it is not my bread and butter and this is
the best I can do.&lt;&#x2F;p&gt;
&lt;p&gt;After restarting&#x2F;re-evaluating our backend and loading the page we will be able to
chat in realtime, no too bad for less than 100 lines of code!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;2023-08-03_19-22-SSE-realtime-updates.png&quot; alt=&quot;realtime-chat&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;I hope you had fun following this simple recipe on how to add a bit of real
time feel to a basic web app! SSE, while simple from the outside, can be handy
in a lot of situations and adding it to your web application can be quite easy.
I am sure that the same can be achieved with other web servers and
&lt;code&gt;core.async&lt;&#x2F;code&gt;, if you&#x27;ll try that approach please write about your experience
and share it with us!&lt;&#x2F;p&gt;
&lt;p&gt;The full source code is available in my playground &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;playground&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;15hbnlj&#x2F;clojure_bites_sse_with_aleph_and_reitit&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;clojurians.slack.com&#x2F;archives&#x2F;C8NUSGWG6&#x2F;p1691087302126619&quot;&gt;Clojurians Slack&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1687165067010195458?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_overview-activity-7092933511426244608-I0bn?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstodon.org&#x2F;@fpsd&#x2F;110829927423436923&quot;&gt;Fediverse&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Migrating to Zola SSG</title>
          <pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/migrating-to-zola/</link>
          <guid>https://fpsd.codes/blog/migrating-to-zola/</guid>
          <description xml:base="https://fpsd.codes/blog/migrating-to-zola/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Recently I switched from &lt;a href=&quot;https:&#x2F;&#x2F;dthompson.us&#x2F;projects&#x2F;haunt.html&quot;&gt;Haunt&lt;&#x2F;a&gt; to &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt; as the static site generator for
this blog. Main reason was a bit of frustration with Haunt&#x27;s documentation
and the CommonMark implementation it was using which was holding me back.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-the-switch&quot;&gt;Why the switch&lt;&#x2F;h1&gt;
&lt;p&gt;When starting this blog I wanted something simple and potentially hackable,
in terms of being able to play with the generator like any other Lisp program,
modifying it a runtime and seeing the result of the change right after it happened.&lt;&#x2F;p&gt;
&lt;p&gt;While Haunt is totally capable of doing so the first roadblock (at least for me)
is that it is written in &lt;a href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;guile&#x2F;&quot;&gt;Guile&lt;&#x2F;a&gt; which I don&#x27;t know at all; given that it is a
Lisp I hoped to get into it quickly, but I&#x27;ve never studied it or played with it
too much, and there is only so much time in a day so I cannot play with all the
fun things that there are around and at the same time hope to get some work done!&lt;&#x2F;p&gt;
&lt;p&gt;So I started looking for alternatives, maybe leaving aside the hackability of the
underlying system in favor of focusing on writing content.&lt;&#x2F;p&gt;
&lt;p&gt;Don&#x27;t get me wrong, Haunt is very nice and customizable, it is not just a toy and
for example it is used to build the &lt;a href=&quot;https:&#x2F;&#x2F;guix.gnu.org&#x2F;&quot;&gt;Guix&lt;&#x2F;a&gt; website (and others); it is possible to
look at that site sources to get an idea on how to write a great website!
The problem is that I did&#x27;n manage to learn few fundamental things, Guile and
Haunt internals, quickly enough to be confident with this system and be productive
with it. Better luck next time, maybe.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;candidates&quot;&gt;Candidates&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;cryogen&quot;&gt;&lt;a href=&quot;http:&#x2F;&#x2F;cryogenweb.org&#x2F;&quot;&gt;Cryogen&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Language: Clojure
Documentation: While complete the feeling is that it does not flow too well&lt;&#x2F;p&gt;
&lt;p&gt;Pros:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You have the engine at your hand and you can hack on it easily while running&lt;&#x2F;li&gt;
&lt;li&gt;Easy to start&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Custom front matter format (EDN)&lt;&#x2F;li&gt;
&lt;li&gt;Takes some time to get into it, not ideal if you just want a quick blog with your style&lt;&#x2F;li&gt;
&lt;li&gt;Documentation could be better&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;nikola-pelican&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;getnikola.com&#x2F;&quot;&gt;Nikola&lt;&#x2F;a&gt;&#x2F;&lt;a href=&quot;https:&#x2F;&#x2F;getpelican.com&#x2F;&quot;&gt;Pelican&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Language: Python
Documentation: Complete&lt;&#x2F;p&gt;
&lt;p&gt;Pros:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Battle tested&lt;&#x2F;li&gt;
&lt;li&gt;Extensible (with lots of extensions)&lt;&#x2F;li&gt;
&lt;li&gt;Lots of themes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Custom front matter&lt;&#x2F;li&gt;
&lt;li&gt;Too big for my needs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;hugo&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gohugo.io&#x2F;&quot;&gt;Hugo&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Language: Go
Documentation: Complete&lt;&#x2F;p&gt;
&lt;p&gt;Pros:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Battle tested&lt;&#x2F;li&gt;
&lt;li&gt;Extensible (with lots of extensions)&lt;&#x2F;li&gt;
&lt;li&gt;Lots of themes (I mean LOTS!)&lt;&#x2F;li&gt;
&lt;li&gt;Comes packaged in a single binary&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I could not find a clear path to get to my first post quickly&lt;&#x2F;li&gt;
&lt;li&gt;Having a lot of features I felt lost in the docs, which caused friction&#x2F;churn&lt;&#x2F;li&gt;
&lt;li&gt;Too big for my needs, the home page says &amp;quot;The world’s fastest framework for building websites&amp;quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;zola&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Language: Rust
Documentation: Complete&lt;&#x2F;p&gt;
&lt;p&gt;Pros:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Documentation was feeling just right for my needs&lt;&#x2F;li&gt;
&lt;li&gt;The home pages says &amp;quot;Your one-stop static site engine&amp;quot;, just what I need&lt;&#x2F;li&gt;
&lt;li&gt;Quick time to first post and custom layout&lt;&#x2F;li&gt;
&lt;li&gt;Lots of themes (and more are coming to it)&lt;&#x2F;li&gt;
&lt;li&gt;Comes packaged in a single binary&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Maybe not as feature rich as others (not a problem for me)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;final-decision&quot;&gt;Final decision&lt;&#x2F;h1&gt;
&lt;p&gt;Admittedly I did not run a very scientific comparison process, what I did was
to try to run a generator with its defaults, hack it a bit and measure:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;time to get to the same layout and a first post&lt;&#x2F;li&gt;
&lt;li&gt;level of frustration while doing so&lt;&#x2F;li&gt;
&lt;li&gt;documentation&lt;&#x2F;li&gt;
&lt;li&gt;…gut feeling&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;What I did not care about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;speed of generation of content&lt;&#x2F;li&gt;
&lt;li&gt;programming language&lt;&#x2F;li&gt;
&lt;li&gt;availability of themes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Following the principle of &amp;quot;least resistance path&amp;quot; I went with Zola because
the process to get to that first post was really painless! Some engines were
not properly documented, with other I felt overwhelmed by options or in the
end it did not feel just right.&lt;&#x2F;p&gt;
&lt;p&gt;It took me 10 minutes to get Zola&#x27;s approach and file structure, another 30
minutes of grunt work to port the front matter of posts from Haunt (I could
have automated it but it was too hot that day and I was not able to think too
much, but it would have been a nice exercise with Babashka!).&lt;&#x2F;p&gt;
&lt;p&gt;While others may be more complete solution to build complex websites, what I
really want is a tiny container for my posts, potentially some other pages
like contact section, bio etc. and Zola was able to deliver in just no time.&lt;&#x2F;p&gt;
&lt;p&gt;Your requirements may be totally different and other engines may work better
for you, all I can suggest is to try (some of) them and see which one sticks
to you!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure Bites - Ring basic auth</title>
          <pubDate>Thu, 06 Jul 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-bites-basic-auth/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-basic-auth/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-basic-auth/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;I am on vacation so I have decided to take it easy and work on small
things on &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt;; one item in the backlog seemed quite approachable:
adding an admin panel to the project, which requires, among other things,
a way to authenticate users to access those restricted sections.&lt;&#x2F;p&gt;
&lt;p&gt;Here is a quick intro to how to add Basic Auth support to a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ring-clojure&#x2F;ring&quot;&gt;Ring&lt;&#x2F;a&gt; web
application, using &lt;a href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~rwv&#x2F;ring-basic-authentication&quot;&gt;ring-basic-authentication&lt;&#x2F;a&gt; middleware, and, as a bonus,
an intro to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.cache&quot;&gt;clojure.core.cache&lt;&#x2F;a&gt; library.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;project-setup&quot;&gt;Project setup&lt;&#x2F;h1&gt;
&lt;p&gt;To show how the middleware works we can create a simple web application with
just one endpoint which later we can protect with basic auth. Start a new
project or use your favorite playground and add the following dependencies:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ring&#x2F;ring-core 1.10.0&lt;&#x2F;li&gt;
&lt;li&gt;ring&#x2F;ring-jetty-adapter 1.10.0&lt;&#x2F;li&gt;
&lt;li&gt;ring-basic-authentication&#x2F;ring-basic-authentication 1.2.0&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feel free to use your preferred project and dependency management tool, here I
show how to do that with tools-deps, it is easy to translate it to &lt;a href=&quot;https:&#x2F;&#x2F;leiningen.org&#x2F;&quot;&gt;lein&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;boot-clj.github.io&#x2F;&quot;&gt;boot&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;resources&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:deps &lt;&#x2F;span&gt;&lt;span&gt;{org.clojure&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.11.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;        ring&#x2F;ring-core {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.10.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;        ring&#x2F;ring-jetty-adapter {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.10.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;        ring-basic-authentication&#x2F;ring-basic-authentication {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.2.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:aliases
&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:dev&#x2F;repl &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;nrepl.cmdline&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;amp;#x2013;middleware&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;[cider.nrepl&#x2F;cider-middleware]&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{cider&#x2F;cider-nrepl {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.31.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;                          djblue&#x2F;portal {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.35.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;                          com.github.jpmonettas&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.11.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;                          com.github.jpmonettas&#x2F;flow-storm-dbg {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;3.3.315&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;             &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:jvm-opts &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-Dclojure.storm.instrumentEnable=true&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span&gt;  }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The extra alias &lt;code&gt;:dev&#x2F;repl&lt;&#x2F;code&gt; is my usual goto setup for development, it is not required
to run the examples but it can be generally handy; feel free to ignore it if you
have your own way.&lt;&#x2F;p&gt;
&lt;p&gt;Now we can create a new src&#x2F;app.clj (or whatever ns you prefer) with the following
content:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; app
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;A namespace used to show how the basic-authentication middleware works&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[ring.adapter.jetty &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; jetty]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Our basic ring handler function, it takes a request map (unused) and
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;   return a response map&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  [_request]
&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:status 200
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:headers &lt;&#x2F;span&gt;&lt;span&gt;{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&#x2F;html&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Hello World&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; We want to hold the server instance in order to close it once done.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; There are better ways to do it, using life cycle&#x2F;state management libraries
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; like mount, integrant, component just to name few, but lets keep it simple
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;server-instance &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;atom &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;nil&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;start-server
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Starts a web server holding its instance in the server-instance atom&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;reset!&lt;&#x2F;span&gt;&lt;span&gt; server-instance
&lt;&#x2F;span&gt;&lt;span&gt;          (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;jetty&#x2F;run-jetty&lt;&#x2F;span&gt;&lt;span&gt; handler {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 3000
&lt;&#x2F;span&gt;&lt;span&gt;                                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join? false&lt;&#x2F;span&gt;&lt;span&gt;})))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;stop-server
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;If there is a server running, stop it and reset the server-instance atom&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap!&lt;&#x2F;span&gt;&lt;span&gt; server-instance
&lt;&#x2F;span&gt;&lt;span&gt;         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[inst]
&lt;&#x2F;span&gt;&lt;span&gt;           (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;when&lt;&#x2F;span&gt;&lt;span&gt; inst
&lt;&#x2F;span&gt;&lt;span&gt;             (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.stop&lt;&#x2F;span&gt;&lt;span&gt; inst)))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;comment
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;start-server&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;stop-server&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we start a REPL and evaluate the buffer (file) we can start the server by
evaluating the fist form in the &lt;code&gt;comment&lt;&#x2F;code&gt; form, and pointing a browser to
&lt;a href=&quot;http:&#x2F;&#x2F;localhost:3000&quot;&gt;http:&#x2F;&#x2F;localhost:3000&lt;&#x2F;a&gt; we should be able to see something like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;basic-ring-handler.png&quot; alt=&quot;basic-handler-output&quot; title=&quot;basic handler output&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;protecting-the-route&quot;&gt;Protecting the route&lt;&#x2F;h1&gt;
&lt;p&gt;The library &lt;code&gt;ring-basic-authentication&lt;&#x2F;code&gt; offers the ring middleware
&lt;code&gt;wrap-basic-authentication&lt;&#x2F;code&gt; that will call a function accepting a username
and a password and returns a truty value on success (authenticated)
or a falsy value otherwise. On success the next middleware (or handler) will be
called, otherwise it will return a 401 response.&lt;&#x2F;p&gt;
&lt;p&gt;The first thing we can do is to write a function that will authenticate (or not) a
request based on provided username and password, here is the simplest implementation
we can write:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;authenticated?
&lt;&#x2F;span&gt;&lt;span&gt;  [username password]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;and &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;secret-username&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; username)
&lt;&#x2F;span&gt;&lt;span&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;secret-password&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; password)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now it is time setup ring to use the middleware to authenticate requests. Lets
re-write the &lt;code&gt;start-server&lt;&#x2F;code&gt; function to do that:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;gen-app
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;wrap-basic-authentication&lt;&#x2F;span&gt;&lt;span&gt; handler authenticated?))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;start-server
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Starts a web server holding its instance in the server-instance atom&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  []
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;reset!&lt;&#x2F;span&gt;&lt;span&gt; server-instance
&lt;&#x2F;span&gt;&lt;span&gt;          (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;jetty&#x2F;run-jetty &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;gen-app&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:port 3000
&lt;&#x2F;span&gt;&lt;span&gt;                                      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:join? false&lt;&#x2F;span&gt;&lt;span&gt;})))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The utility function &lt;code&gt;gen-app&lt;&#x2F;code&gt; returns a new handler using the &lt;code&gt;wrap-basic-authentication&lt;&#x2F;code&gt;
middleware to wrap our request handler; start server calls the &lt;code&gt;gen-app&lt;&#x2F;code&gt; function
when starting the server, to get the application handler function.&lt;&#x2F;p&gt;
&lt;p&gt;We can see it in action in the following screenshot&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;basic-auth-form.png&quot; alt=&quot;basic-auth-form&quot; title=&quot;basic auth form&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now for each request this is the flow:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ring calls the app handler&lt;&#x2F;li&gt;
&lt;li&gt;the basic auth middleware is called and&lt;&#x2F;li&gt;
&lt;li&gt;if successful it will return the result of calling the wrapped handler&lt;&#x2F;li&gt;
&lt;li&gt;if not it will return a 401 without even calling the wrapper handler&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In ring world this is the usual flow; in general we can expect to have chain of
middlewares before the call to the request can even start, common middlewares can
be used for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;request routing: given a path call a specific handler&lt;&#x2F;li&gt;
&lt;li&gt;request parameter parsing: parsed parameters, coming from query string, body etc are injected for later use&lt;&#x2F;li&gt;
&lt;li&gt;response transformation: for example to serialize to JSON, XML or other formats&lt;&#x2F;li&gt;
&lt;li&gt;session, authentication and authorization handling&lt;&#x2F;li&gt;
&lt;li&gt;request logging and metrics&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are quite a lot of already existing and useful middlewares that can be
used off the shelf.&lt;&#x2F;p&gt;
&lt;p&gt;Even if this approach can be good enough for some small and private services it
can become unusable or hard to maintain pretty easily if the service grows or must
be exposed to the public internet; few issues that can come to mind:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Hardcoded credentials: even if the project sources can be private it is easy to leak them, better to load them from another source&lt;&#x2F;li&gt;
&lt;li&gt;If we want to change credentials a new build is required; not the end of the world but not really convenient&lt;&#x2F;li&gt;
&lt;li&gt;There is no way to have different handlers and URL with or without the basic auth&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;lets-make-it-flexible&quot;&gt;Lets make it flexible&lt;&#x2F;h1&gt;
&lt;p&gt;First thing we can do to improve this example is to get rid of those ugly hardcoded
credentials.&lt;&#x2F;p&gt;
&lt;p&gt;One way could be to load the username and password from environment variables; as one
extra step would be to on the safe side we can avoid having defaults so that if
we forget to set the environment variables no one can access the restricted
resources. Here is one possible way to &lt;code&gt;authenticated?&lt;&#x2F;code&gt; for our new requirements:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[secret-username (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth-username&lt;&#x2F;span&gt;&lt;span&gt; env &amp;quot;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;      secret-password (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth-password&lt;&#x2F;span&gt;&lt;span&gt; env &amp;quot;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;authenticated?
&lt;&#x2F;span&gt;&lt;span&gt;    [username password]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;and &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;seq&lt;&#x2F;span&gt;&lt;span&gt; secret-username) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; making sure this two are set to something
&lt;&#x2F;span&gt;&lt;span&gt;         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;seq&lt;&#x2F;span&gt;&lt;span&gt; secret-password) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; TIL that (seq &amp;quot;&amp;quot;) is equivalent to (not (empty? &amp;quot;&amp;quot;)), thanks clj-kondo!
&lt;&#x2F;span&gt;&lt;span&gt;         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; secret-username username)
&lt;&#x2F;span&gt;&lt;span&gt;         (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; secret-password password))))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Wait wait wait, where is this &lt;code&gt;env&lt;&#x2F;code&gt; coming from?&lt;&#x2F;p&gt;
&lt;p&gt;Even if is possible to get the value of environment variables using Java
interop via &lt;code&gt;System&#x2F;getenv&lt;&#x2F;code&gt; I have decided to introduce the library &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;environ&quot;&gt;environ&lt;&#x2F;a&gt;
because it offers a Clojure only interface and provides more goodies like
reading environment variables from env files or JVM properties and has a
nicer interface (IMHO).&lt;&#x2F;p&gt;
&lt;p&gt;To be able to use this library all we need to do is
to add it to our &lt;code&gt;deps.edn&lt;&#x2F;code&gt; (or…you know) and refer the &lt;code&gt;env&lt;&#x2F;code&gt; symbol in our
namespace. For more details please refer to the library docs, all we need to
know now is that it behaves like a normal map, at least for reading.&lt;&#x2F;p&gt;
&lt;p&gt;Oh, one more thing, keywords are translated to &amp;quot;UPPER&lt;sub&gt;SNAKE&lt;&#x2F;sub&gt;&lt;sub&gt;CASE&lt;&#x2F;sub&gt;&amp;quot; when looking
them up, in our case &lt;code&gt;:basic-auth-username&lt;&#x2F;code&gt; will lookup &lt;code&gt;BASIC_AUTH_USERNAME&lt;&#x2F;code&gt;
environmental variable.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;even-more-flexible&quot;&gt;Even more flexible&lt;&#x2F;h1&gt;
&lt;p&gt;Now we want to give access to the protected resource to more people and usually
sharing the same credentials is not a good practice; what happens if we want
to invalidate the access for one of the users? We should create new credentials
and share it only with the ones whom should have access to the resource. It is
not really practical, is it? So at this point we may want to have user specific
credentials but handling them with env vars can be too complicated or unpractical.&lt;&#x2F;p&gt;
&lt;p&gt;An option is to store credentials in a file that we can read at the start of the
application, in case someone leaves our organization we can update our credentials
file and restart the app. For convenience we can read the credentials file path
from an environment variable, we already know how to do it right? Right.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get-credentials
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Return a username -&amp;amp;gt; password map reading it form the specified file, or an
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;   empty map it reading fails&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  [credentials-path]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;try
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;with-open &lt;&#x2F;span&gt;&lt;span&gt;[r (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;io&#x2F;reader&lt;&#x2F;span&gt;&lt;span&gt; credentials-path)]
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;edn&#x2F;read &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;java.io.PushbackReader.&lt;&#x2F;span&gt;&lt;span&gt; r)))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;catch&lt;&#x2F;span&gt;&lt;span&gt; Throwable  _ {})))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;authenticated?
&lt;&#x2F;span&gt;&lt;span&gt;  [username password]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[credentials (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-credentials &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth-credentials&lt;&#x2F;span&gt;&lt;span&gt; env &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;credentials.edn&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt; credentials username) password)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now &lt;code&gt;authenticated?&lt;&#x2F;code&gt; will read the credentials from an edn file whose path is taken
from the &lt;code&gt;BASIC_AUTH_CREDENTIALS&lt;&#x2F;code&gt; environment variable (defaulting to some well
known path). If the credentials will ever change we must restart the application
to see the new values, not the end of the world but we can do a bit better.&lt;&#x2F;p&gt;
&lt;p&gt;The idea is to cache the credentials with a TTL, so when it expires we can read
the file again with possibly new values. Implementing a cache with TTL is a nice
exercise but we can leverage the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.cache&quot;&gt;clojure.core.cache&lt;&#x2F;a&gt; library that implements
this functionality for us. I am not going to write about this library in detail,
the documentation does a great job anyway, so I&#x27;ll focus on the bare minimum
to get things done.&lt;&#x2F;p&gt;
&lt;p&gt;Assuming that we have added the library to our dependencies and required it in
our namespace, we can change &lt;code&gt;autheticated?&lt;&#x2F;code&gt; to make use of it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; create a TTL cache with an empty map and a ttl of 60 seconds
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;def &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;credentials-cache &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cache&#x2F;ttl-cache-factory &lt;&#x2F;span&gt;&lt;span&gt;{} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:ttl 60000&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;authenticated?
&lt;&#x2F;span&gt;&lt;span&gt;  [username password]
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;[credentials (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cache&#x2F;lookup-or-miss
&lt;&#x2F;span&gt;&lt;span&gt;                      credentials-cache
&lt;&#x2F;span&gt;&lt;span&gt;                      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:credentials &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; we try to lookup the key :credentials in the cache
&lt;&#x2F;span&gt;&lt;span&gt;                      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;[_]      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; if the key is missing or expired we load the credentials file
&lt;&#x2F;span&gt;&lt;span&gt;                        (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get-credentials &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:basic-auth-credentials&lt;&#x2F;span&gt;&lt;span&gt; env &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;credentials.edn&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))))]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt; credentials username) password)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a sorts of an hack of the TTL cache; usually you want to cache with TTL different keys,
and load the value of those keys (from DB, external APIs, whatever) when the TTL expires. In our
case we just want to load the whole file so we are using the key &lt;code&gt;:credentials&lt;&#x2F;code&gt; to hold its
content. The clojure.core.cache implements quite a lot of caching strategies, I encourage everyone
to give it a try!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusions&quot;&gt;Conclusions&lt;&#x2F;h1&gt;
&lt;p&gt;If dealing with a simple application, using basic auth can be good enough
and we have explored different ways to achieve that; clearly, as the application
grows, other authentication mechanisms like a full suited identity&#x2F;role management
solution may be a better fit. Currently I am exploring &lt;a href=&quot;https:&#x2F;&#x2F;www.keycloak.org&quot;&gt;Keycloak&lt;&#x2F;a&gt;, hoping to come
up with a library for Clojure.&lt;&#x2F;p&gt;
&lt;p&gt;Sources for this little project can be found in my shiny new playground &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;playground&quot;&gt;repo&lt;&#x2F;a&gt;,
specifically in the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;playground&#x2F;-&#x2F;blob&#x2F;main&#x2F;src&#x2F;codes&#x2F;fpsd&#x2F;ring_basic_auth.clj&quot;&gt;ring_basic_auth.clj&lt;&#x2F;a&gt; file&lt;&#x2F;p&gt;
&lt;h1 id=&quot;alternatives&quot;&gt;Alternatives&lt;&#x2F;h1&gt;
&lt;p&gt;Every web server provides a way to protect one (or more) endpoint at the
server configuration level, sometimes it can be even convenient to
leverage that functionality; the only downside I see in this approach is
that it couples the application to the web server serving it.
If the endpoint structure is sufficiently complex it is easy to forget
something in case of a migration to a different web server.&lt;&#x2F;p&gt;
&lt;p&gt;Another alternative is to use the same authentication&#x2F;authorization mechanism
used for normal users and play with roles and permissions to enable&#x2F;disable
access to specific resources. In the longer term this is the approach I
would take given that it reduces special cases and makes role management
more consistent.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;14s2fje&#x2F;clojure_bites_ring_basic_auth&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;dev.to&#x2F;fpsd&#x2F;clojure-bites-ring-basic-auth-2jf2&quot;&gt;Dev.to&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1676869804030844928?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_clojure-bites-ring-basic-auth-activity-7082636181598781440-Dihi?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Skateboarding, keep track of your progress</title>
          <pubDate>Thu, 29 Jun 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/skateboarding/</link>
          <guid>https://fpsd.codes/blog/skateboarding/</guid>
          <description xml:base="https://fpsd.codes/blog/skateboarding/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Keep track of your skateboarding progress, set your goals
and create a plan to achieve them at your own pace.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;possible-names&quot;&gt;Possible names&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;skoach&lt;&#x2F;li&gt;
&lt;li&gt;coach360&lt;&#x2F;li&gt;
&lt;li&gt;pops&lt;&#x2F;li&gt;
&lt;li&gt;scoop&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;application-platform&quot;&gt;Application platform&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;website, mostly for offline activity like loading media, set plans&lt;&#x2F;li&gt;
&lt;li&gt;mobile app, for real time updates&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;features&quot;&gt;Features&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;record where you are, tricks you own trick you want to get&lt;&#x2F;li&gt;
&lt;li&gt;set monthly&#x2F;weekly&#x2F;daily plans and get stats&lt;&#x2F;li&gt;
&lt;li&gt;gamification, achievements, challenges&lt;&#x2F;li&gt;
&lt;li&gt;suggest plans and workout activities&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;copy-for-landing&quot;&gt;Copy for landing&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;random-ideas&quot;&gt;Random ideas&lt;&#x2F;h2&gt;
&lt;p&gt;From skateboarders to skateboarders.&lt;&#x2F;p&gt;
&lt;p&gt;Land that trick that is always escaping you, keep track of your
progress, learn from your mistakes.&lt;&#x2F;p&gt;
&lt;p&gt;Skateboarding is freedom, and that great feeling when you land a
trick.&lt;&#x2F;p&gt;
&lt;p&gt;Keep yourself accountable, share your progress with your crew, set
personal and group challenges.&lt;&#x2F;p&gt;
&lt;p&gt;If it is not recorded it didn&#x27;t happen, record yourself and share
the videos on social media platforms.&lt;&#x2F;p&gt;
&lt;p&gt;Achieve your goals and unlock new challenges.&lt;&#x2F;p&gt;
&lt;p&gt;Remember to have fun!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;business-model&quot;&gt;Business model&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Freemium&lt;&#x2F;li&gt;
&lt;li&gt;Pro with subscription or LTD&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;longer-term-vision&quot;&gt;Longer term vision&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;For amateurs going pro&lt;&#x2F;li&gt;
&lt;li&gt;For teams, either brand or national&lt;&#x2F;li&gt;
&lt;li&gt;Live coaching, talk to a pro, show videos and get help&lt;&#x2F;li&gt;
&lt;li&gt;Workout coach&lt;&#x2F;li&gt;
&lt;li&gt;Tools for content creation&lt;&#x2F;li&gt;
&lt;li&gt;Public challenges with sponsors&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Just release Unrefined&#x27;s commit 300, an update!</title>
          <pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/unrefined-commit-300/</link>
          <guid>https://fpsd.codes/blog/unrefined-commit-300/</guid>
          <description xml:base="https://fpsd.codes/blog/unrefined-commit-300/">&lt;h1 id=&quot;just-deployed-commit-300&quot;&gt;Just deployed commit 300!&lt;&#x2F;h1&gt;
&lt;p&gt;I was working on Unrefined, tested some changes, fixed a bug, cooked a release
and suddenly I&#x27;ve realized that it was commit 300! This requires an update post
for sure ;)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bugs&quot;&gt;Bugs&lt;&#x2F;h2&gt;
&lt;p&gt;We use &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt; at work and from time to time we experience some weird
behaviors, usually nothing too serious the blocks us but at least one bug
was ruining a refinement session.&lt;&#x2F;p&gt;
&lt;p&gt;We were refining a ticket together with the product manager and we found that
it had to rewritten to make it possible to estimate it. The ticket was broken
down to smaller one and when we came back to the original one, as usual we
started the process to estimate it again and Unrefined start throwing exceptions!
The problem was that a refinement + ticket id are defined as unique and trying
to start a new session for that ticket raise an exception from the DB layer.
We worked around this issue but I was not too happy, so I&#x27;ve created an issue
which I&#x27;ve closed today.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;restructuring-code&quot;&gt;Restructuring code&lt;&#x2F;h2&gt;
&lt;p&gt;The namespaces that make up the application were all over the place and did
not use the pattern &amp;quot;tld.your-domain.app-name&amp;quot; so I&#x27;ve restructured everything
and now all the namespaces are living inside &amp;quot;one.unrefined&amp;quot; namespace; it is
not the most important change in the world but given that I&#x27;ve started working
on the frontend side with the correct namespace model I wanted to be consistent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;clojurescript&quot;&gt;ClojureScript&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve started working on the cheatsheet customization code for the frontend, it
started as a separate project to experiment with it, choosing the framework
(the good old &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tonsky&#x2F;rum&quot;&gt;rum&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lilactown&#x2F;helix&quot;&gt;helix&lt;&#x2F;a&gt;? went with the familiar one, rum) and the build system
trying out shadow-cljs over the more familiar figwheel-main. After some try and
error I&#x27;ve decided to stick with shadow-cljs because it will be easier to pull
in Javascript dependencies. I still like figwheel-main especially if used with
ClojureScript only solutions (for example &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cjohansen&#x2F;dumdom&#x2F;&quot;&gt;dumdom&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;The custom cheatsheet is still in WIP state and I hope to release it as
soon as possible, it should be a game changer for Unrefined, preparing the road
for user accounts.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wip&quot;&gt;WIP&lt;&#x2F;h2&gt;
&lt;p&gt;Some parts of Unrefined are poorly or not tested at all, the core logic is but
I&#x27;d like to change this in the near future. It will be almost mandatory as soon
as more users and organizations will use this tool.&lt;&#x2F;p&gt;
&lt;p&gt;Also pending, and depending on user auth, is some sort of SSO or OAuth to leverage
existing platforms like Jira, Asana and friends.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;closing&quot;&gt;Closing&lt;&#x2F;h1&gt;
&lt;p&gt;My goal with this post is to give an update on Unrefined and what is coming next,
it could have been a tweet but it is too long :D&lt;&#x2F;p&gt;
&lt;p&gt;See you on commit 400! But most probably even earlier ;)&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure digressions - Setting up a playground</title>
          <pubDate>Fri, 23 Jun 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-digressions-setup-playground/</link>
          <guid>https://fpsd.codes/blog/clojure-digressions-setup-playground/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-digressions-setup-playground/">&lt;h1 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;&#x2F;h1&gt;
&lt;p&gt;Why a new series named Clojure Digressions?&lt;&#x2F;p&gt;
&lt;p&gt;Clojure Bites is more suited for short introductions to Clojure
related libraries and tools, instead with digressions I&#x27;d like to
talk about more general topics, like development environment setup,
good&#x2F;bad habits and personal development as a Clojure developer.&lt;&#x2F;p&gt;
&lt;p&gt;Like a conversation ice breaker, I start by talking about my personal
take on some specific argument, looking forward to get input from
other, possibly more experienced, developers on some specific topic.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;A playground is a safe place aimed at experimentation, without any
legacy background limiting your fantasy and willingness to explore
solutions outside your usual comfort zone i.e. all the code you have
already written for your company&#x2F;product.&lt;&#x2F;p&gt;
&lt;p&gt;It is no secret that as professional developers we are expected to reuse
as much as possible of what we have at hand in our code base, being careful
when adding new dependencies, limit experimentation to when it is really
really needed to avoid introducing unforeseen costs but at the same time we
love to try out new things! What if we had a safe place to experiment,
just taking out a small bit of our business domain and test it against
a different approach?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;starting-from-scratch&quot;&gt;Starting from scratch&lt;&#x2F;h2&gt;
&lt;p&gt;You want to quickly sketch out something, no REPL is running, the first
thing you can do is to fire up an interactive REPL by running &lt;code&gt;$ clojure&lt;&#x2F;code&gt;
or, if you want readline support, &lt;code&gt;$ clj&lt;&#x2F;code&gt;; now you have a prompt where you
con evaluate forms using the rich clojure.core library. Nothing too advanced,
the same is available with the default Python, Ruby and many other prompts.
But it is still something.&lt;&#x2F;p&gt;
&lt;p&gt;Lets spice up our REPL by replacing the standard readline lib with
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bhauman&#x2F;rebel-readline&quot;&gt;rebel&lt;&#x2F;a&gt; that will give us autocompletion,
hints and much more. To try it out quickly:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Sdeps &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{:deps {com.bhauman&#x2F;rebel-readline {:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.1.4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;}}}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -m&lt;&#x2F;span&gt;&lt;span&gt; rebel-readline.main
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The `-Sdeps` is used to pull in more dependencies, the `-m` parameter is used to
specify an entry point to be called; this will initialize a REPL session using
rebel readline interface. Learn more &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bhauman&#x2F;rebel-readline&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;ll like it you can create an alias in your `~&#x2F;.clojure&#x2F;deps.edn` file in
order to be able to start a rebel REPL without being forced to remember all the
required parameters, here is an example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:aliases &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:rebel &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{com.bhauman&#x2F;rebel-readline {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.1.4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts  &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rebel-readline.main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And invoke it simply with:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -M&lt;&#x2F;span&gt;&lt;span&gt;:rebel
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;asciinema.org&#x2F;a&#x2F;160597&quot;&gt;asciinema&lt;&#x2F;a&gt; of the official docs is worth one
billion words ;)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;beyond-throw-away-sessions&quot;&gt;Beyond throw away sessions&lt;&#x2F;h2&gt;
&lt;p&gt;Working directly in the REPL prompt is not really ergonomic in some cases and you may
prefer the comfort of your favorite IDE or editor to hack on some code; the first
step is to start a REPL to which you can attach to. As with rebel readline it not
required to to have an alias in your $HOME&#x2F;.clojure&#x2F;deps.edn or project&#x27;s deps.edn:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Sdeps &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{:deps {nrepl&#x2F;nrepl {:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.0.0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;}}}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -m&lt;&#x2F;span&gt;&lt;span&gt; nrepl.cmdline
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;nREPL&lt;&#x2F;span&gt;&lt;span&gt; server started on port 39435 on host localhost - nrepl:&#x2F;&#x2F;localhost:39435
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can connect your IDE to the REPL and start hacking! Please notice that the
port may change at each invocation. Deciding what to work on is left as an exercise
to the reader :)&lt;&#x2F;p&gt;
&lt;p&gt;Like we did for rebel readline, we can add an alias to our $HOME&#x2F;.clojure&#x2F;deps.edn
file to make it easier in future to start a REPL we can attach to:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:aliases &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:rebel &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{com.bhauman&#x2F;rebel-readline {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.1.4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts  &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rebel-readline.main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:nrep  &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{nrepl&#x2F;nrepl {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.0.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts  &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;nrepl.cmdline&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Starting it with:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -M&lt;&#x2F;span&gt;&lt;span&gt;:nrepl
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point I encourage you to test this setup with your own IDE to get a real feel
of it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;some-more-spices&quot;&gt;Some more spices&lt;&#x2F;h2&gt;
&lt;p&gt;Being able to run a REPL on demand is already something useful but we can get more
out of it, for example being able to pull in new dependencies on demand in the
currently running process using the upcoming &lt;code&gt;add-lib&lt;&#x2F;code&gt; in the &lt;code&gt;repl.deps&lt;&#x2F;code&gt; namespace,
see &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;news&#x2F;2023&#x2F;04&#x2F;14&#x2F;clojure-1-12-alpha2&quot;&gt;here&lt;&#x2F;a&gt; for more details.&lt;&#x2F;p&gt;
&lt;p&gt;Add a new alias to our clojure deps file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:aliases &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:rebel &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{com.bhauman&#x2F;rebel-readline {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.1.4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts  &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rebel-readline.main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:add-libs &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{clojure.org&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.12.0-alpha3&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}}
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:nrep  &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{nrepl&#x2F;nrepl {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.0.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:main-opts  &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-m&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;nrepl.cmdline&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can add this capability to either rebel or nrepl alias, for example, running:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -M&lt;&#x2F;span&gt;&lt;span&gt;:add-libs:rebel
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;will start a new REPL pulling in both the new Clojure version with support to dynamically
load libraries and a rebel readline prompt; the same would apply if replacing rebel with
nrepl, i.e. we would have a nrepl session with the add-libs support.&lt;&#x2F;p&gt;
&lt;p&gt;It is useful to be able to combine more functionalities coming from different aliases when
starting a new REPL process. Again I encourage everyone to play with it and come with a setup
the covers your requirements.&lt;&#x2F;p&gt;
&lt;p&gt;Other useful tools worth considering for your personal toolbox:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;djblue&#x2F;portal&quot;&gt;Portal&lt;&#x2F;a&gt;: A clojure tool to navigate through your data.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jpmonettas&#x2F;flow-storm-debugger&quot;&gt;FlowStorm&lt;&#x2F;a&gt;: A tracing debugger for Clojure and ClojureScript.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;jpmonettas.github.io&#x2F;flow-storm-debugger&#x2F;user_guide.html#_clojurestorm&quot;&gt;ClojureStorm&lt;&#x2F;a&gt;: Companion FlowStorm Clojure implementation that automatically instruments your code to be used with FlowStorm.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;eval&#x2F;deps-try&quot;&gt;deps-try&lt;&#x2F;a&gt;: an add on for rebel-readline to try out new libraries if you don&#x27;t like the upcoming add-libs.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It can be seen as a safe place to play with unknown possible solutions,
in an isolated environment, collect useful information and then decide
if we can introduce them to our daily stack.&lt;&#x2F;p&gt;
&lt;p&gt;How can it be useful?&lt;&#x2F;p&gt;
&lt;p&gt;Which tools should I include in mine?&lt;&#x2F;p&gt;
&lt;p&gt;Is there something obvious that I am missing?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;are-we-playground-yet&quot;&gt;Are we playground yet?&lt;&#x2F;h2&gt;
&lt;p&gt;We have learned how to setup an interactive or a nREPL to connect to from an
IDE and we have added some useful tools to improve our developer experience
but where do we put the results of our experiments? I usually start a new
project each time I want try out something and honestly it is a straight
forward process especially if using tools like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;seancorfield&#x2F;clj-new&quot;&gt;clj-new&lt;&#x2F;a&gt;, taking from the project
README:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# one-off to install clj-new as a tool:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Ttools&lt;&#x2F;span&gt;&lt;span&gt; install com.github.seancorfield&#x2F;clj-new &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{:git&#x2F;tag &amp;quot;v1.2.399&amp;quot;}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; :as clj-new
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# commands to create new projects:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# create a new app:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Tclj-new&lt;&#x2F;span&gt;&lt;span&gt; app :name myname&#x2F;myapp
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# create a new library:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Tclj-new&lt;&#x2F;span&gt;&lt;span&gt; lib :name myname&#x2F;mylib
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# create a new template:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Tclj-new&lt;&#x2F;span&gt;&lt;span&gt; template :name myname&#x2F;mytemplate
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# create a new project from a public template:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clojure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -Tclj-new&lt;&#x2F;span&gt;&lt;span&gt; create :template electron-app :name myname&#x2F;myelectron-app#+END_SRC
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Recently maybe I have created too many throw away projects and creating a full
fledged project structure each time feels like a waste of time; ideally I&#x27;d like
to open a new file, connect to a fully capable REPL (or start a new one) and
start hacking. When finished commit the new file or changes to existing ones and
move one.&lt;&#x2F;p&gt;
&lt;p&gt;One advantage I see with this approach is that I can keep all these small projects
in one place, accumulating knowledge that can be easily reused later. An added
benefit is that it is easy to add another powerful tool like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nextjournal&#x2F;clerk&quot;&gt;Clerk&lt;&#x2F;a&gt; to generate
playbooks to interact with and document the code; two months from now you may
want to get back to your experiment and easily get back to it and its conclusions
without spending too much time.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;We started from a simple REPL, added some tasty juice to it and ended up creating
a safe place to play and experiment. I want to point out that this is just my personal
take and not a default in the Clojure community (or if that is, it is just a coincidence).&lt;&#x2F;p&gt;
&lt;p&gt;I am curious to learn how others are addressing the same problem, if it is a problem anyway.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;14gy1qb&#x2F;setting_up_a_playground_environment&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1672224281709641728?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;dev.to&#x2F;fpsd&#x2F;clojure-digressions-setting-up-a-playground-1lo0&quot;&gt;Dev.to&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstodon.org&#x2F;@fpsd&#x2F;110593734613643881&quot;&gt;Fediverse&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_clojure-digressions-setting-up-a-playground-activity-7078054057298280448-dOGE?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Opening wave 2022-06-16</title>
          <pubDate>Fri, 16 Jun 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/opening-wave-2023-06-16/</link>
          <guid>https://fpsd.codes/blog/opening-wave-2023-06-16/</guid>
          <description xml:base="https://fpsd.codes/blog/opening-wave-2023-06-16/">&lt;h1 id=&quot;starting-wave-2023-06-16&quot;&gt;Starting Wave 2023-06-16&lt;&#x2F;h1&gt;
&lt;p&gt;After a two days break I am ready to open the new wave, that will last
one month like the previous one, I feel comfortable with this pace!&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s in the plan? It be broken down to the following items.&lt;&#x2F;p&gt;
&lt;p&gt;Before diving into the wave&#x27;s goals, the usual link to the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;10&quot;&gt;MR&lt;&#x2F;a&gt; 
that will collect all the work being done and shared later in this site.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unrefined&quot;&gt;Unrefined&lt;&#x2F;h2&gt;
&lt;p&gt;Given the seminal work of the previous wave the plan is to finish the
customization of cheat sheets. The initial implementation of the UI is
quite rough but I can focus on improving it later if there is enough
interest, now it is time to embed it into the app. The extension may
come next but realistically in the next wave.&lt;&#x2F;p&gt;
&lt;p&gt;Another key addition to &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt;, pending for
too long, is the landing page and some content explaining what problems
it is trying to solve and how, namely estimation bias and why the estimation
cheat sheet is a core function of the flow.&lt;&#x2F;p&gt;
&lt;p&gt;Authentication has been mentioned before and delayed for too long, it is
now time to add it, probably using an external identity management platform
like &lt;a href=&quot;https:&#x2F;&#x2F;clerk.com&quot;&gt;Clerk&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fpsd-codes&quot;&gt;fpsd.codes&lt;&#x2F;h2&gt;
&lt;p&gt;The plan is to continue publishing at least two Clojure Bite articles and
updates regarding product development; posts about Unrefined will be
replicated in the upcoming application blog.&lt;&#x2F;p&gt;
&lt;p&gt;If possible I&#x27;d like to also include updates on the second organization
the should be onboarded to Unrefined, if it will even happen ;).&lt;&#x2F;p&gt;
&lt;p&gt;Redesigning the general layout, like grouping posts be month, adding
contact pages etc will be postponed to the next wave.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;live-streaming&quot;&gt;Live streaming&lt;&#x2F;h2&gt;
&lt;p&gt;Still unsure how to drive more value while streaming, I guess I&#x27;ll
keep trying to learn how to make something out of it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;&#x2F;h2&gt;
&lt;p&gt;This post sets the start of the new wave, the feeling is that the plan
is more clear compared to the previous one, I hope that the closing post
will confirm that it was a good plan, or at least that I&#x27;ve learned how
to improve for the next time, see you around the net!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1669763899858583557?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_starting-wave-2023-06-16-activity-7075543297842192385-f7CN?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.indiehackers.com&#x2F;post&#x2F;my-build-in-public-process-and-operating-system-hows-yours-be50155f11&quot;&gt;IndieHackers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Closing wave 2022-05-17</title>
          <pubDate>Mon, 12 Jun 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/closing-wave-2023-05-17/</link>
          <guid>https://fpsd.codes/blog/closing-wave-2023-05-17/</guid>
          <description xml:base="https://fpsd.codes/blog/closing-wave-2023-05-17/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Every wave is a great source of learning material, built on top of the
previous experience and the expectations I set for myself. So what
have I learned this time?&lt;&#x2F;p&gt;
&lt;p&gt;To summarize:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;reaching out to the builders&#x2F;solopreneurs community is a force multiplier&lt;&#x2F;li&gt;
&lt;li&gt;time management is still an issue but on its way to be addressed&lt;&#x2F;li&gt;
&lt;li&gt;same for time allocation, should I spend more time writing or building?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In this post I&#x27;ll try to put down an honest analysis of the past wave, and
set the ground for the next one.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;some-metrics&quot;&gt;Some metrics&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;fpsd-codes&quot;&gt;fpsd.codes&lt;&#x2F;h3&gt;
&lt;p&gt;First of all the raw numbers, the blog have reached the all time high of 1K
unique visitors! It does not look like a lot, and it isn&#x27;t, but I have to
consider that the main traffic comes from Clojure Bites posts, and it is a very
small niche. I enjoy writing those posts, they seem useful in general and not
just related to my effort to create and advertise my products…but they help in
that sense too.&lt;&#x2F;p&gt;
&lt;p&gt;Main traffic sources are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reddit&lt;&#x2F;li&gt;
&lt;li&gt;Twitter&lt;&#x2F;li&gt;
&lt;li&gt;Linkedin&lt;&#x2F;li&gt;
&lt;li&gt;Dev.to&lt;&#x2F;li&gt;
&lt;li&gt;Clojure Deref&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are other sources but those are both irrelevant and surprisingly I
don&#x27;t market my posts there! Weird but interesting.&lt;&#x2F;p&gt;
&lt;p&gt;I can confirm that some channels perform better than others depending on
the content of the posts; more technical posts perform great in Reddit
because it is easy to directly reach the interested community. Twitter
performs well too, probably because some aggregators like &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;planetclojure&quot;&gt;PlanetClojure&lt;&#x2F;a&gt;
will re-post to interested communities. A new entry in this wave is
&lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;news&#x2F;2023&#x2F;06&#x2F;09&#x2F;deref&quot;&gt;Clojure Deref&lt;&#x2F;a&gt;, a weekly update about all things Clojure; it collects,
videos, podcasts, blogs and libraries updates, highly suggested!
To try in future, again for Clojure related posts, is the channel
#news-and-articles in &lt;a href=&quot;https:&#x2F;&#x2F;clojurians.slack.com&#x2F;messages&quot;&gt;Clojurians Slack&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;One trick that I have learned is to publish a Clojure related post on Wednesday
or Thursday in order to be featured in the next edition of Clojure Deref.
Friday works terribly and I haven&#x27;t tried earlier in the week but I am
usually ready to publish on Wednesdays so, yeah, there is no incentive in
trying other schedules.&lt;&#x2F;p&gt;
&lt;p&gt;Linkedin and Twitter are great places to talk about projects updates and
everything related to the &amp;quot;Build in public&amp;quot; community. I can highly
recommend getting in touch with the people posting with the tag #buildinpublic
on Twitter, and get invited to the &amp;quot;Build, Show and Tell&amp;quot; meetup, the
&lt;a href=&quot;http:&#x2F;&#x2F;buildshowandtell.com&#x2F;&quot;&gt;website&lt;&#x2F;a&gt; is not finished yet, but a great design is coming soon!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;unrefined&quot;&gt;Unrefined&lt;&#x2F;h3&gt;
&lt;p&gt;Unrefined user growth is still at 1 user, 0 paying customers. The reason being
that it needs one necessary change, pointed out by many, more on that later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;engaging-with-the-community&quot;&gt;Engaging with the community&lt;&#x2F;h2&gt;
&lt;p&gt;Just to repeat myself, engage with your community! Look for people living the
problem you want to solve and get in touch with them; if you are building something
it also helps follow the #buildinpublic community on Twitter, amazing people are
gathering to help each other with their own projects in an honest and transparent way.
The community is growing and this is the best time to get in touch with them (us? ;) ).&lt;&#x2F;p&gt;
&lt;p&gt;Last week it was my turn to present the project I am working on (&lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt;) and I&#x27;ve
got the best feedback ever! The presentation and discussion was extremely smooth and
friendly and, believe me, even if you are shy you will feel like you are talking to
old time and supportive friends! Another underrated factor is that you can also
provide great feedback to other people, learn from them and grow all together, so
give yourself a favor and reach out to this amazing community!&lt;&#x2F;p&gt;
&lt;p&gt;Coming back to my personal effort to reach out to communities that could be interested
in using Unrefined, I can say that it has been totally not existent and clearly this
must be fixed in the upcoming months. Note taken…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;time-management&quot;&gt;Time management&lt;&#x2F;h2&gt;
&lt;p&gt;During the past wave I have experienced a bit of stress, trying to do too much at the
same time, especially considering the limited time budget. What has suffered the most
was Unrefined, with almost no development or marketing dedicated to it. During the
current wave, which this post is closing, I have taken a more relaxed approach and I
think it worked; probably I am not the most productive guy on the planet but at least
I&#x27;ve got:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;First, fundamental change to Unrefined to make it possible to select a different cheat sheet&lt;&#x2F;li&gt;
&lt;li&gt;One Clojure Bites post every two weeks; I am aiming at one per week but I have to build a queue&lt;&#x2F;li&gt;
&lt;li&gt;Engage with the community of builders which gave me a boost in motivation and productivity&lt;&#x2F;li&gt;
&lt;li&gt;Tried to stream two times; this is a time and energy consuming activity and something is telling me to try it more&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;My plans for the next wave is to stick to this schedule and possibly try to get some
more but it is like a stretch goal. I don&#x27;t want to feel stressed for no reason and
brake a functioning routine.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;projects&quot;&gt;Projects&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;unrefined-1&quot;&gt;Unrefined&lt;&#x2F;h3&gt;
&lt;p&gt;This wave have seen the advent of the most requested feature, being able to select
different cheat sheets that may be more relevant to companies routines and processes.&lt;&#x2F;p&gt;
&lt;p&gt;I am currently working on an interface to enable the customization of the cheat sheet,
the second most request feature. After that I want to enable signing in, a foundational
work to give more value to prospect paying customers. This would enable providing stats
on past refinements but the value is yet to be validated.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fpsd-codes-1&quot;&gt;fpsd.codes&lt;&#x2F;h3&gt;
&lt;p&gt;The goal with this website is to stick to at least one Clojure Bites every other week,
and possibly add more content; I&#x27;d like to give more updates on Unrefined or other
projects (especially in the style of small bets).&lt;&#x2F;p&gt;
&lt;p&gt;I have a backlog of changes regarding the layout structure, post grouping, enabling comments
and so on but lets see. I have some free days at the end of the month, I hope to make use
of them!&lt;&#x2F;p&gt;
&lt;p&gt;One idea in the back burner is a new series &amp;quot;Clojure Meals&amp;quot; covering bigger topics or deep
dives. Maybe Once a month? Every other month?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;streaming&quot;&gt;Streaming&lt;&#x2F;h3&gt;
&lt;p&gt;This is a new thing for me, I am learning everything from scratch and it is time consuming,
for each minute spent streaming I think it costs 30 minutes to 1 hour of preparation. The
value I can see is that it forces me to build in public and get in the flow, I&#x27;d like to have
some form of interaction but there is no audience yet LOL. I&#x27;ll try it some more just to see.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;I am still learning my way out there in the wild word of internet, I am having fun and
learning. Maybe all this effort will not be useful in the near future but I bet on the
compounding effect of all activities.&lt;&#x2F;p&gt;
&lt;p&gt;This post closes the wave 2023-05-17, see you on the next wave!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - Rendering HTML</title>
          <pubDate>Thu, 08 Jun 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-bites-selmer/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-selmer/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-selmer/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;There are many ways to produce HTML in Clojure, from template engines
to data oriented DSLs and React wrappers. Each one has it own use cases,
benefits and downsides so there is no one solution that fits all problems.&lt;&#x2F;p&gt;
&lt;p&gt;In this post we will have a look at &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yogthos&#x2F;Selmer&quot;&gt;selmer&lt;&#x2F;a&gt;, a library that offers a well known approach for HTML templating, by the prolific 
&lt;a href=&quot;https:&#x2F;&#x2F;yogthos.net&quot;&gt;Dmitri Sotnikov&lt;&#x2F;a&gt; (AKA Yogthos).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;selmer&quot;&gt;Selmer&lt;&#x2F;h2&gt;
&lt;p&gt;Since the beginning of time, template engines have ruled the world of web
development, in a form or another. That is how the majestic PHP has come to
life, and it is still out there and kicking.&lt;&#x2F;p&gt;
&lt;p&gt;Other notable mentions in the same space are Jinja for Python, Erc for Ruby,
Mustache&#x2F;Handlebars for JS, and probably many, many more for all of the
available languages anyone can think of.&lt;&#x2F;p&gt;
&lt;p&gt;In Clojure world &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yogthos&#x2F;Selmer&quot;&gt;selmer&lt;&#x2F;a&gt; is a very nice library that will look familiar to Jinja
users, something to consider if you are coming from that kind of experience.&lt;&#x2F;p&gt;
&lt;p&gt;Pros of this kind of approach:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;knowledge is easily transferable between languages&lt;&#x2F;li&gt;
&lt;li&gt;familiarity of templating language&lt;&#x2F;li&gt;
&lt;li&gt;easy interop with web designers, they provide you the HTML and you enrich it with dynamic content&lt;&#x2F;li&gt;
&lt;li&gt;easy to use&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;What follows in an example of how a template may look like, assuming that we
want to render the index of a blog, with a list of posts and a preview of their
content; do not worry, all the building blocks will be explained later.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{blog.title}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Latest posts&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  {% for post in blog.posts %}
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{post.title}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{post.sections.0.body|abbrev&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;a &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;href&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{{post.url}}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;gt;Full article&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  {% endfor %}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this example are visible to main (and only) building blocks of the template
engine offered by selmer:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;{{ placeholders }}&lt;&#x2F;li&gt;
&lt;li&gt;{{ placeholder|filters }}&lt;&#x2F;li&gt;
&lt;li&gt;{% tags %}&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;placeholders&quot;&gt;Placeholders&lt;&#x2F;h3&gt;
&lt;p&gt;Placeholders are identified by double curly braces, for example {{blog.tile}},
and essentially take the content of a variable and render it to the output,
taking from the blog example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{blog.title}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assuming that title&#x27;s value is &amp;quot;My awesome heading&amp;quot;, the generated output will
be:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;My awesome heading&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Placeholders can also access nested data structures like maps and sequences
using the dot notation; building on top of the previous example assume that
we have a map representing a blog post with title and sections and we want
to render a preview, showing just title and the first section, example map&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:title &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;A blog post&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:sections &lt;&#x2F;span&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:title &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Section 1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Some very long text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;              {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:title &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Section 2&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:body &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Some other long text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}]}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the preview template, assuming that we want to show the preview of the
first section of a post.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{post.title}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{post.sections.0.title}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{post.sections.0.body}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When rendered, the output will look like this&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-html &quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;A blog post&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Section 1&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Some very long text&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;filters&quot;&gt;Filters&lt;&#x2F;h3&gt;
&lt;p&gt;Filters can be piped to placeholders to alter their values before being sent
to the output, the syntax is {{placeholder|filter1|filter2:extra_param|…filterN}}.&lt;&#x2F;p&gt;
&lt;p&gt;These are essentially functions that takes as the first argument the result of the
previous step, plus other optional parameters, and return a value to either be
rendered or piped to the next filter. There are a lot of builtin filters and it is
possible to add custom ones.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yogthos&#x2F;Selmer#filters&quot;&gt;Here&lt;&#x2F;a&gt; is a list of the builtin filters.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tags&quot;&gt;Tags&lt;&#x2F;h3&gt;
&lt;p&gt;Tags provide a way to introduce logic inside templates, offering conditionals,
iterations and composition via inheritance and fragment template loading, which
enables template code reuse.&lt;&#x2F;p&gt;
&lt;p&gt;Commonly used tags are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;for (seen it in the first example)&lt;&#x2F;li&gt;
&lt;li&gt;if: conditionally render some part of the template&lt;&#x2F;li&gt;
&lt;li&gt;include: takes another template and renders it in the current one&lt;&#x2F;li&gt;
&lt;li&gt;extend: enables template inheritance&lt;&#x2F;li&gt;
&lt;li&gt;block: commonly used with inheritance, make it possible to inject code in a parent template&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are many more and like filters, tags can be extended to add more functionalities
to your templates.&lt;&#x2F;p&gt;
&lt;p&gt;Please refer to the official &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yogthos&#x2F;Selmer#tags&quot;&gt;docs&lt;&#x2F;a&gt; for more details.&lt;&#x2F;p&gt;
&lt;p&gt;As a side &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;144io42&#x2F;comment&#x2F;jnpy2p8&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;note&lt;&#x2F;a&gt;, selmer can be used out side
the HTML rendering context, as a generic template engine even if I
focus on this aspect in this post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;other-options&quot;&gt;Other options&lt;&#x2F;h2&gt;
&lt;p&gt;Of course there are other options available but I am not going to cover them in
this post. Worth mentioning:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;hiccup&quot;&gt;Hiccup&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;r0man&#x2F;sablono&quot;&gt;Sablono&lt;&#x2F;a&gt;: data oriented DSL commonly used in frameworks like rum or reagent&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lilactown&#x2F;helix&quot;&gt;Helix&lt;&#x2F;a&gt;: thin wrapper around modern React, uses react&#x2F;dom to render components&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;fhd&#x2F;clostache&quot;&gt;Clostache&lt;&#x2F;a&gt;: mustache for Clojure; looks a quite unmaintained but maybe you can resurrect it!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let me know if I have missed something or if you want a deep dive of another library,
I am constantly looking for new topics to cover in this humble website.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;144io42&#x2F;clojure_bites_render_html_introducing_selmer&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;dev.to&#x2F;fpsd&#x2F;clojure-bites-render-html-introducing-selmer-templating-library-3bh8&quot;&gt;Dev.to&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1666890435271225345?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_clojure-bites-rendering-html-activity-7072657271607971841-FTxU?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - Structured logging with mulog</title>
          <pubDate>Wed, 24 May 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-bites-mulog/</link>
          <guid>https://fpsd.codes/blog/clojure-bites-mulog/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-bites-mulog/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Logging is a fundamental tool to monitor and debug a running system.
Even if we can use logs to gather metrics about our system, these are
often written with the assumption that humans are going to consume them,
which make it hard to extract meaning information from log messages.&lt;&#x2F;p&gt;
&lt;p&gt;Log messages are rarely consistent or even meaningful if read few days
after writing them during your emergency debug session. Writing good log
messages is hard! Almost as hard as naming things and cache invalidation.&lt;&#x2F;p&gt;
&lt;p&gt;Another overlooked problem is that often logs are are context less, what
can we infer by a message like &amp;quot;Failed to process user payment&amp;quot; if we don&#x27;t
know what triggered the payment process, the affected user or product?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;structured-logging&quot;&gt;Structured logging&lt;&#x2F;h2&gt;
&lt;p&gt;Structured logging aims to provide query-able, consistent, information rich
logs that can be used for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Business intelligence: derive business relevant data from logged events&lt;&#x2F;li&gt;
&lt;li&gt;Monitoring: understand the current state of a system&lt;&#x2F;li&gt;
&lt;li&gt;Debugging: understand the context in which an error has been reported&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;mulog&quot;&gt;mulog&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrunoBonacci&#x2F;mulog&quot;&gt;mulog&lt;&#x2F;a&gt; is a Clojure library, developed by Bruno Bonacci, that can be used
to replace your text based logs with content rich events, enabling a large
set of analytics for the data produced by your system. I highly suggest to
read the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrunoBonacci&#x2F;mulog#motivation&quot;&gt;motivation&lt;&#x2F;a&gt; behind this library to understand the author&#x27;s vision
but, for the impatient, it can be summarized as following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Text based logs are hard to write and hard to parse&lt;&#x2F;li&gt;
&lt;li&gt;Logs are generally written with the assumption that humans will consume that, making them less useful for analytics&lt;&#x2F;li&gt;
&lt;li&gt;Text logs often have no context&lt;&#x2F;li&gt;
&lt;li&gt;Logs with extra data are serialized as text and de-serialized when ingesting them, a clear waste of resources&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;&#x2F;h2&gt;
&lt;p&gt;Without further ado, lets jump to some examples!&lt;&#x2F;p&gt;
&lt;p&gt;We can either create a new project adding mulog to the dependencies
or start a REPL with support to clojure.repl.deps&#x2F;add-lib and
download the lib dynamically to our session [older post with add-lib].&lt;&#x2F;p&gt;
&lt;p&gt;New project&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; clj&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span&gt;:new-app :name mulog-example.main
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; cd mulog-main
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The :new-app alias is defined in my $HOME&#x2F;.clojure&#x2F;deps.edn, using the
handy &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;seancorfield&#x2F;clj-new&quot;&gt;clj-new&lt;&#x2F;a&gt; tool by Sean Corfield; the project is well documented and
the README is a great place to get you up and running, in case you are
interested to try it out. It is also possible to start from scratch with
a basic deps.edn file like the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:deps &lt;&#x2F;span&gt;&lt;span&gt;{org.clojure&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.11.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;           com.brunobonacci&#x2F;mulog {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.9.0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assuming a fully functional project&#x2F;REPL&#x2F;whatever setup with mulog
available to you it is time to write our first log:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;require &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;[com.brunobonacci.mulog &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; u])
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;::my-first-event :message &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;a log, wow!&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I want to point out few things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the first parameter is the event name, it is a good practice to use a fully qualified keyword&lt;&#x2F;li&gt;
&lt;li&gt;the event name is followed by as many key&#x2F;value pairs as we need to enrich the log event&lt;&#x2F;li&gt;
&lt;li&gt;if we evaluate that code nothing is logged anywhere! what?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;publishers&quot;&gt;Publishers&lt;&#x2F;h2&gt;
&lt;p&gt;mulog make it possible to send events to different publishers which will take
care of delivering the events to the right destinations. Out of the box mulog
supports console and file publishers but there are my others available to be
plugged in. It is also possible to write your own publisher if your target tools
is not supported yet. A list of currently supported publishers is available &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;com.brunobonacci&#x2F;mulog&#x2F;0.9.0&#x2F;doc&#x2F;publishers&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For the sake of this example we can enable the console publisher&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;start-publisher! &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:type :console&lt;&#x2F;span&gt;&lt;span&gt;}) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;;  good enough for development
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After evaluating it, we will be able to see the event logged to our REPL.
By default an event will look like the following map:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;trace-id   &lt;&#x2F;span&gt;&lt;span&gt;#mulog&#x2F;flake &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;4q6x5wlEnU0WNV5fV0HTvJ32Lz3b4GvV&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;timestamp  1684906031447
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;event-name :your-ns&#x2F;my-first-event
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;namespace  &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;your-ns&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:message          &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;a log, wow!&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Keys in the mulog namespace are automatically added by the library, the
rest is whatever we have provided as extra parameters in the call.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;context&quot;&gt;Context&lt;&#x2F;h2&gt;
&lt;p&gt;Enriching our logs with custom data is the first step towards our goal
to improve the observability of our systems. Another key factor is the
consistency of the data collected with logs.&lt;&#x2F;p&gt;
&lt;p&gt;As systems grow in size and complexity it might be hard and tedious
to maintain the same set of keys across all functions and entry points.&lt;&#x2F;p&gt;
&lt;p&gt;Assuming that we are writing a web application we may want to keep a
standard set of key across all event generate by all handlers, for example
user id, ip, endpoint path or name and so on.&lt;&#x2F;p&gt;
&lt;p&gt;mulog events are logged within a context and we have already seen it in
action in our first log, and it was represented by the keys in mulog
namespace.&lt;&#x2F;p&gt;
&lt;p&gt;It is possible to add more data to the logging context using:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; (u&#x2F;set-global-contex! {:key1 val1 &amp;amp;#x2026; keyN valN}) alters the global context, may be used at application start for example
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; (u&#x2F;with-context {:keyN valN} body) macro that will wrap the body in a new, temporary context; more context can be chained together
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;with-context &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:username &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;foo&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;                   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler :some-endpoint&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;                   (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;::business-logic :message &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;User did something&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; will log something look this
&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:username &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;foo&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:handler :some-endpoint
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;trace-id &lt;&#x2F;span&gt;&lt;span&gt;#mulog&#x2F;flake &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;4q6yXTTvky4yTeHENi8UcRNPpjJrPpPq&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;timestamp 1684907603865
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;event-name :user&#x2F;business-logic
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;namespace &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:message &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;User did something&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As we have seen in the example, the event has been created with the extra
information set in the call to u&#x2F;with-context. This is very handy and makes
it easier to have consistent logs across our code.&lt;&#x2F;p&gt;
&lt;p&gt;As we add more context in our call chain we will end up with extremely rich
logs that can be used for analytics. Taking again the example of a web app
we can potentially understand how different parts of our business are being
used by users with different devices using the request user agent or how
many users we have for each country using the request ip, in order to scale
up or down our clusters in those regions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;traces&quot;&gt;Traces&lt;&#x2F;h2&gt;
&lt;p&gt;On top of the logging facility, mulog u&#x2F;trace macro make it possible to measure
the time spent by any part of code wrapped by it, here is its signature:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;trace&lt;&#x2F;span&gt;&lt;span&gt; event-name [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:keyN&lt;&#x2F;span&gt;&lt;span&gt; value1N] body)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Like u&#x2F;log it can leverage nested context data but on top of that it is possible
to introduce new context data that will be captured by the current and nested u&#x2F;trace
calls but not by nested calls to u&#x2F;log.&lt;&#x2F;p&gt;
&lt;p&gt;The body parameter is the code that we want to trace. Here is an example call
and the resulting event:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;u&#x2F;trace &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:calling-api &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;post-to-external-api&lt;&#x2F;span&gt;&lt;span&gt; payload))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; example event logged by the trace call
&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;duration 250362049
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;namespace &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;outcome :ok
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; &amp;lt;- here is the extra content provided by trace
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;parent-trace nil
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;root-trace &lt;&#x2F;span&gt;&lt;span&gt;#mulog&#x2F;flake &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;4q73JVUr9GK_PBOhs6p3NJfnYhqps7Tp&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;timestamp 1684953980473
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;trace-id &lt;&#x2F;span&gt;&lt;span&gt;#mulog&#x2F;flake &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;4q73JVUr9GK_PBOhs6p3NJfnYhqps7Tp&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mulog&#x2F;event-name :calling-api&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The example shown here traces within the boundaries of the running process but
mulog can be instrumented to work with distributed tracing systems; for more
details refer to the &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;com.brunobonacci&#x2F;mulog&#x2F;0.9.0&#x2F;doc&#x2F;readme&quot;&gt;documentation&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;transformers&quot;&gt;Transformers&lt;&#x2F;h2&gt;
&lt;p&gt;When starting a publisher it is possible to specify a transformation function
that will take a sequence of events to be modified or even evicted, and returns
the new sequence of events. Thanks Bruno for your &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;13qvind&#x2F;comment&#x2F;jljaawv&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;review&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;This approach can be useful to remove sensible data before it is sent to a
log collector, for example user tokens, passwords, API keys and so on.
While we may want to retain this kind of information when developing, and
using a console logger, it is mandatory to remove it while in production
when working with real business data. Another use case is to completely evict
events that we don&#x27;t want to send to a specific collector, for example we
may have a collector interested in metrics to scale a cluster and another
one dedicated to business logic, the possibilities are endless!&lt;&#x2F;p&gt;
&lt;p&gt;Having the possibility to specify a transformation function at the publisher
level make it easy to configure our systems based on the environment they
are running on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;While it looks simple on the surface, mulog is a powerful library that helps you
to get useful insights about your running system. Fortunately it does not require
a lot of ceremony to get started and can be bent to meet your specific use cases
if needed.&lt;&#x2F;p&gt;
&lt;p&gt;This post is just a brief introduction to the topic of structured logging, viewed
with the lenses of mulog. I highly suggest to dig deeper in its &lt;a href=&quot;https:&#x2F;&#x2F;cljdoc.org&#x2F;d&#x2F;com.brunobonacci&#x2F;mulog&#x2F;0.9.0&#x2F;doc&#x2F;readme&quot;&gt;documentation&lt;&#x2F;a&gt; and
watch Bruno&#x27;s talk &amp;quot;&lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=P1149dWnl3k&quot;&gt;u&#x2F;log and the next 100 logging systems&lt;&#x2F;a&gt;&amp;quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1661456289778618374?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;13qvind&#x2F;mulog_and_structured_logging&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_clojure-bites-structured-logging-with-mulog-activity-7067224425963692032-91VM?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstodon.org&#x2F;@fpsd&#x2F;110433421066504045&quot;&gt;Fediverse&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Plans to improve the estimation cheat sheet</title>
          <pubDate>Sat, 20 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/unrefined-new-cheatsheet/</link>
          <guid>https://fpsd.codes/blog/unrefined-new-cheatsheet/</guid>
          <description xml:base="https://fpsd.codes/blog/unrefined-new-cheatsheet/">&lt;h1 id=&quot;project&quot;&gt;Project&lt;&#x2F;h1&gt;
&lt;p&gt;Project name: Unrefined&lt;&#x2F;p&gt;
&lt;p&gt;Project URL: &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;https:&#x2F;&#x2F;unrefined.one&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Project sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;cheat-sheet-redesign&quot;&gt;Cheat sheet redesign&lt;&#x2F;h1&gt;
&lt;p&gt;The current cheat sheet is not ideal for the following reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It is hardcoded and there is no way to chose a different one&lt;&#x2F;li&gt;
&lt;li&gt;It is too specific to one use case&#x2F;company&lt;&#x2F;li&gt;
&lt;li&gt;The estimation UI with the sliders is not intuitive, hiding the maximum story points&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;breakdown-of-tasks&quot;&gt;Breakdown of tasks&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;change-ui-to-select-a-cheat-sheet-from-a-list-of-available-ones&quot;&gt;Change UI to select a cheat sheet from a list of available ones&lt;&#x2F;h3&gt;
&lt;p&gt;When starting an estimation provide the option to select the cheat sheet to be
used during the refinement session from a list of available ones, possibly with
a preview of the breakdown items.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;sub-tasks-size-m&quot;&gt;Sub tasks - Size M&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;[BE] Provide the list of available cheat sheets to the frontend layer with their details&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Show the pre-selected cheat sheet name&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Send the selected cheat sheet to the backend when starting a refinement&lt;&#x2F;li&gt;
&lt;li&gt;[BE] Use the cheat sheet sent by the frontend when creating a new refinement&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Enable selecting a different cheat sheet from a list of available ones&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Show the breakdown items of the currently selected cheat sheet&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;change-ui-of-the-estimation-page&quot;&gt;Change UI of the estimation page&lt;&#x2F;h3&gt;
&lt;p&gt;Explore few different designs for the estimation page to enable new ways of
selecting the story points for each breakdown item. Implement at least one
and provide a way to select between available styles, starting from the
current one. If a user changes the style, set it as default in the local
storage.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;sub-tasks-size-s&quot;&gt;Sub Tasks - Size S&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;Try a couple of designs before implementing them&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Implement at least one new design&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Enable selecting from available designs&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Set the selected design as the default in the local storage&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Read the selected design from local storage and use that if available otherwise use the default one&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;customize-the-cheat-sheet&quot;&gt;Customize the cheat sheet&lt;&#x2F;h3&gt;
&lt;p&gt;After selecting a cheat sheet, the user can customize it, creating a new
cheat sheet.&lt;&#x2F;p&gt;
&lt;p&gt;It should be possible to start from a blank page if the current templates
do not match the user&#x27;s use case.&lt;&#x2F;p&gt;
&lt;p&gt;Evaluate if it is feasible to store the new cheat sheet somewhere, considering
that at the moment there is no way to authenticate a user.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;sub-tasks-size-l&quot;&gt;Sub Tasks - Size L&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;[FE] Enable cheat sheet customization, adding or removing breakdowns and their items&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Send the custom cheat sheet to the backend when starting a new refinement&lt;&#x2F;li&gt;
&lt;li&gt;[BE] Accept new cheat sheets from the frontend and use them when creating a refinement&lt;&#x2F;li&gt;
&lt;li&gt;[BE] Send the custom cheat sheet to the frontend in estimation and result pages&lt;&#x2F;li&gt;
&lt;li&gt;[FE] Use custom cheat sheets in the estimation and result pages&lt;&#x2F;li&gt;
&lt;li&gt;Evaluate how to store and retrieve custom cheat sheets&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Build, show &amp; tell</title>
          <pubDate>Thu, 18 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/build-show-and-tell/</link>
          <guid>https://fpsd.codes/blog/build-show-and-tell/</guid>
          <description xml:base="https://fpsd.codes/blog/build-show-and-tell/">&lt;h1 id=&quot;tl-dr-connect-with-your-community-right-now&quot;&gt;TL;DR Connect with your community! Right, now!!!&lt;&#x2F;h1&gt;
&lt;p&gt;Yesterday, May 16 2023, 19:00 CEST, I&#x27;ve attended a spontaneous
meetup of indiehackers, organized on Twitter by &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;gyulanemeth85&quot;&gt;GYN&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Somehow he managed to take a group of strangers, posting under the
hashtag &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;hashtag&#x2F;buildinpublic?src=hashtag_click&quot;&gt;#buildinpublic&lt;&#x2F;a&gt;, and gather everyone together to talk about
what we are building, what kind of help we are after and how we can
help others. And was great!&lt;&#x2F;p&gt;
&lt;p&gt;Here is where it all &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;gyulanemeth85&#x2F;status&#x2F;1658885816498651180?s=20&quot;&gt;started&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-meetup&quot;&gt;The meetup&lt;&#x2F;h2&gt;
&lt;p&gt;We started introducing ourselves, our background and our projects,
after that &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;axmacinsky&quot;&gt;Alexander&lt;&#x2F;a&gt; introduced his projects and I&#x27;ve assisted to
the most honest and insightful round of feedback that anyone can
receive in her&#x2F;his life by complete strangers!&lt;&#x2F;p&gt;
&lt;p&gt;Honestly it has been amazing, ideas floated around, from UX to
monetizing, to cost analysis and more. And just to repeat, it was
coming from people that have never interacted in real or virtual life!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;I wrote this tiny post to encourage everyone to get in touch with their
own community, you&#x27;ll never know how supportive it can be until you try
it. It is easy to set the boundaries to your (home) office and users&#x27;
feedback, but if don&#x27;t get in touch with other experiencing the same
challenges as yours, I can confidently say that you are missing out.&lt;&#x2F;p&gt;
&lt;p&gt;Please come and join the &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;buildinpublic&quot;&gt;#buildinpublic&lt;&#x2F;a&gt; community, if don&#x27;t like it you
lose nothing but I suspect that we have a lot to share and learn from
each other.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Opening wave 2023-05-17</title>
          <pubDate>Wed, 17 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/wave-2023-05-17-opening/</link>
          <guid>https://fpsd.codes/blog/wave-2023-05-17-opening/</guid>
          <description xml:base="https://fpsd.codes/blog/wave-2023-05-17-opening/">&lt;p&gt;Wave merge request: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;9&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;9&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Past wave had a bit ups and downs, following the white rabbit of
number of views on this website but not moving the needle of &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;product&lt;&#x2F;a&gt;
awareness, nor investigating other opportunities. A key factor was
(poor) time management, as reported while closing wave 2023-04-26.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-round&quot;&gt;Next round&lt;&#x2F;h2&gt;
&lt;p&gt;This wave should focus on the failures of the previous one, trying
to address the issues that have had the most impact on the expected
progress, such as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Balance: main driving forces are content creation and product development, how to respect both?&lt;&#x2F;li&gt;
&lt;li&gt;Progress: how to make sure energy is spent in meaningful activities&lt;&#x2F;li&gt;
&lt;li&gt;Time management: how to make use of the limited time available&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;projects&quot;&gt;Projects&lt;&#x2F;h2&gt;
&lt;p&gt;As always there is work to do for this website, Unrefined and, this
time I am adding a new idea that I&#x27;d like to validate.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;website&quot;&gt;Website&lt;&#x2F;h3&gt;
&lt;p&gt;Topics:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Clojure Bites: I am enjoying writing this series of posts, realistically I want to have one every two weeks&lt;&#x2F;li&gt;
&lt;li&gt;Project progress: not much happened recently, I want to try to have an update once a week&lt;&#x2F;li&gt;
&lt;li&gt;Project validation: this is a new thing, I have a couple of ideas in mind but I have to scope them well before starting&lt;&#x2F;li&gt;
&lt;li&gt;Video contents: I&#x27;d like to record a session about the last Clojure Bites and how to use Unrefined; it is a time consuming activity&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;unrefined&quot;&gt;Unrefined&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;d like to use some of the feedback I&#x27;ve got recently to move
the project to a state where it is more generally useful and usable:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Estimation cheat sheet: it is an interesting concept that must be generalized&lt;&#x2F;li&gt;
&lt;li&gt;Cheat sheet customization: it is hard to make a generic one and I should enable people to customize it&lt;&#x2F;li&gt;
&lt;li&gt;Estimation breakdown UI: the current slider approach can be improved, I&#x27;ve collected a couple of ideas worth trying&lt;&#x2F;li&gt;
&lt;li&gt;User account + OAuth providers: if users can customize their cheat sheets they may want to keep it with their own accounts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;validating-a-new-idea&quot;&gt;Validating a new idea&lt;&#x2F;h3&gt;
&lt;p&gt;A general advice is to build something that solve your problems
with the assumption that others may have the same problem. But
before building the solution is better to validate that there is
enough market for it both in terms of potential users and $$$.
This is the indie hacker&#x2F;bootstrapper 101, and given I&#x27;m a noob
better to start from the basics!&lt;&#x2F;p&gt;
&lt;p&gt;So the plan is:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Find where the potential audience hang out&lt;&#x2F;li&gt;
&lt;li&gt;Understand their needs&lt;&#x2F;li&gt;
&lt;li&gt;Create a landing page and collect potential users&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll reveal the project and results in a future post, until then
stay tuned!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stats-update&quot;&gt;Stats update&lt;&#x2F;h3&gt;
&lt;p&gt;Here is the graph since the beginning of this site, it awesome how
it growed in just one month! Peaks are for the Clojure Bites series
but just because it is easier to target specific communities, and
not because those were great articles :) &lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;wave-2023-05-17-opening&#x2F;images&#x2F;all-time-metrics-2023-05-17_22-09.png&quot; alt=&quot;stats&quot; title=&quot;stats&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_opening-wave-2023-05-17-activity-7064699255726182401-TYjQ?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1658934922843701250?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Closing wave 2022-04-26</title>
          <pubDate>Mon, 15 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/closing-wave-2023-04-26/</link>
          <guid>https://fpsd.codes/blog/closing-wave-2023-04-26/</guid>
          <description xml:base="https://fpsd.codes/blog/closing-wave-2023-04-26/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Closing the first wave, I feel the need of a sort of retrospective, pointing
out what went well, what didn&#x27;t and action items for the next iteration.&lt;&#x2F;p&gt;
&lt;p&gt;TL;DR is that I&#x27;ve probably set ambitious goals for myself (maybe too much),
there are two driving forces competing for my limited time, the content creation
side and the product development side, and I have to find a way to balance
my time budget.&lt;&#x2F;p&gt;
&lt;p&gt;Continue reading for the gory details.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wave-goals&quot;&gt;Wave goals&lt;&#x2F;h2&gt;
&lt;p&gt;From the start of the wave&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Improving the estimation cheat sheet in Unrefined&lt;&#x2F;li&gt;
&lt;li&gt;Creating a landing page and marketing it (throwing some $$$ in marketing maybe)&lt;&#x2F;li&gt;
&lt;li&gt;Improving this website, there are still some information missing&lt;&#x2F;li&gt;
&lt;li&gt;Reveal the new experiment and first impressions&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-went-well&quot;&gt;What went well&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;improving-the-estimation-cheat-sheet-in-unrefined&quot;&gt;Improving the estimation cheat sheet in Unrefined&lt;&#x2F;h3&gt;
&lt;p&gt;This is a mixed bag, on one hand I&#x27;ve received some feedback that the cheat sheet
is a good idea, but in the current form is not that useful. I&#x27;ve started working
on a generic one but it is not an easy task but now it is clear that I can try
to create some default templates and let people customize their own.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;creating-a-landing-page-and-marketing-it-throwing-some-in-marketing-maybe&quot;&gt;Creating a landing page and marketing it (throwing some $$$ in marketing maybe)&lt;&#x2F;h3&gt;
&lt;p&gt;Baby steps, I have added a brief description of how Unrefined works; next step
should be to get a pricing page proposing &amp;quot;Pro&amp;quot; features and measure engagement.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;more-content-and-hints-about-what-kind-of-posts-drive-more-visits&quot;&gt;More content and hints about what kind of posts drive more visits&lt;&#x2F;h3&gt;
&lt;p&gt;In this wave I&#x27;ve published two (&lt;a href=&quot;&#x2F;clojure-bites---clojuretestare.html&quot;&gt;1&lt;&#x2F;a&gt;, &lt;a href=&quot;&#x2F;clojure-bites---dynamically-add-depencencies-at-runtime.html&quot;&gt;2&lt;&#x2F;a&gt;)
short articles on Clojure which performed quite well given my small network.
One lesson is that this kind of posts do not perform well on Linkedin, on the other
hand Reddit and Twitter convert pretty well. Also timing is crucial, better to post
early or mid week.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-didn-t&quot;&gt;What didn&#x27;t&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;improving-this-website-there-are-still-some-information-missing&quot;&gt;Improving this website, there are still some information missing&lt;&#x2F;h3&gt;
&lt;p&gt;Nothing done yet, plus I wanted to record a screen cast but I&#x27;ve spent too much time
learning OBS and I was not happy with the final result. I have the video still
in my todo list and I want to release it soon!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;reveal-the-new-experiment-and-first-impressions&quot;&gt;Reveal the new experiment and first impressions&lt;&#x2F;h3&gt;
&lt;p&gt;No work done here as well. I&#x27;m still figuring out the scope of the project which
at this time looks way too big for a single person. I&#x27;ve got other ideas that
I&#x27;d like to validate. The biggest blockers are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Defining scope and core idea&lt;&#x2F;li&gt;
&lt;li&gt;Learning how to quickly build landing pages, it is not a tech problem, more of copy writing&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;marketing-unrefined&quot;&gt;Marketing Unrefined&lt;&#x2F;h3&gt;
&lt;p&gt;It was planned to record a video session of how it works, create a landing page
and push it to the wild. None of this happened.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;time-management&quot;&gt;Time management&lt;&#x2F;h3&gt;
&lt;p&gt;Currently I am spending most of my time on content creating and not enough on current
projects, adding meaningful features or potential projects that require some research.&lt;&#x2F;p&gt;
&lt;p&gt;On content creation, I am still finding my way to it and it is time consuming but I suspect
there is a lot of potential there, to drive traffic to products or possibly becoming
a product itself. The general suggestion is to try to find verticals, and this is a whole
big topic to explore.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;action-items&quot;&gt;Action items&lt;&#x2F;h2&gt;
&lt;p&gt;Looking at the broader picture I think I have to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Get better at managing time: with a full time job every &amp;quot;free&amp;quot; time matters!&lt;&#x2F;li&gt;
&lt;li&gt;Build an operating system that makes sense: connected to previous point; having a process should help with time management as well&lt;&#x2F;li&gt;
&lt;li&gt;Keep ambitious goals, even if I haven&#x27;t reached most of my goals I think it is better to aim higher than lower&lt;&#x2F;li&gt;
&lt;li&gt;Try out more ideas i.e. get better at pushing out landing pages and looking for early feedback&#x2F;validation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;It is important to remind to my(your)self that building something new is hard but
also exciting! There are a lot of unknowns and we learn as we go, so please try to find
the time to look back at what you did, where you want to be and look forward the next
steps.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - dynamically add depencencies at runtime!</title>
          <pubDate>Fri, 12 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/clojure-add-lib/</link>
          <guid>https://fpsd.codes/blog/clojure-add-lib/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-add-lib/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;One pain point of REPL driven development is that even if it is possible to
change an application while it is running, if you want to experiment with a
new library it is need to add a new dependency to your project and restart the
REPL to use it, possibly breaking your flow.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately a new set of functions to alleviate this pain are in the &lt;a href=&quot;https:&#x2F;&#x2F;clojure.atlassian.net&#x2F;browse&#x2F;CLJ-2761&quot;&gt;works&lt;&#x2F;a&gt;
and ready to be tested since Clojure &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;releases&#x2F;devchangelog#v1.12.0-alpha2&quot;&gt;1.12.0-alpha2&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;add-lib: Given a lib that is not yet on the repl classpath, make it available by downloading the library if necessary and adding it to the classloader.&lt;&#x2F;li&gt;
&lt;li&gt;add-libs: Given lib-coords, a map of lib to coord, will resolve all transitive deps for the libs together and add them to the repl classpath, unlike separate calls to add-lib.&lt;&#x2F;li&gt;
&lt;li&gt;sync-deps: Calls add-libs with any libs present in deps.edn but not yet present on the classpath.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;current-status&quot;&gt;Current status&lt;&#x2F;h2&gt;
&lt;p&gt;At the time of this post, add-lib is not yet in an official Clojure release
but can be added as a development dependency, possibly putting it in an alias
using for development; it does not make too much sense to have it bundled in
your production build anyway.&lt;&#x2F;p&gt;
&lt;p&gt;Assuming that you have a `:dev&#x2F;repl` alias in your deps.edn file, you can get
this new functionality by adding version 1.12.0-alpha2 of Clojure to your deps:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:dev&#x2F;repl &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{org.clojure&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.12.0-alpha2&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;add-lib-in-action&quot;&gt;add-lib in action&lt;&#x2F;h2&gt;
&lt;p&gt;As an example I am going to re-write the FizzBuzz example from a previous post
to use pattern matching instead of cond.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;initial-setup&quot;&gt;Initial setup&lt;&#x2F;h3&gt;
&lt;p&gt;First step is to setup deps.edn to use Clojure 1.12.0-alpha2 in a development
alias, it is a best practice to have development namespaces added to the source
path, used to call add-lib, in order to not ship it with the production release.&lt;&#x2F;p&gt;
&lt;p&gt;Another good habit is to have a custom user namespace with code used during
development, usually storing its code in dev&#x2F;user.clj; to make it available in
the repl, the &amp;quot;dev&amp;quot; directory must be added to :extra-deps vector.&lt;&#x2F;p&gt;
&lt;p&gt;Add the following map to the :aliases section of deps.edn map&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:dev&#x2F;repl &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-deps &lt;&#x2F;span&gt;&lt;span&gt;{org.clojure&#x2F;clojure {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:mvn&#x2F;version &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1.12.0-alpha2&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}}
&lt;&#x2F;span&gt;&lt;span&gt;              &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:extra-paths &lt;&#x2F;span&gt;&lt;span&gt;[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]}}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Put the following content in dev&#x2F;user.clj&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; user
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[clojure.repl.deps &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[add-lib]]))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;scenario&quot;&gt;Scenario&lt;&#x2F;h3&gt;
&lt;p&gt;For the sake of argument assume that we want to write a new
implementation of FizzBuzz, in order to impress future recruiters.&lt;&#x2F;p&gt;
&lt;p&gt;Looking around we find out that &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Pattern_matching&quot;&gt;pattern matching&lt;&#x2F;a&gt; is a interesting
approach to conditionals compared to ifs and conds, commonly used in
functional programming languages (for ex. Erlang, Elixir and more) but
also being implemented in more traditional languages like Python
(as of Python 3.10).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;changing-fizzbuzz-to-use-pattern-matching&quot;&gt;Changing FizzBuzz to use pattern matching&lt;&#x2F;h3&gt;
&lt;p&gt;Ready to approach our new mission we fire up our IDE, load the FizzBuzz
project and start a repl, maybe running tests again to ensure that
nothing broke while we were not watching.&lt;&#x2F;p&gt;
&lt;p&gt;Looking around we find &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.match&quot;&gt;core.match&lt;&#x2F;a&gt; and, wow, the first example is an
implementation of FizzBuzz, how lucky!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;require &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;[clojure.core.match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[match]])
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;doseq &lt;&#x2F;span&gt;&lt;span&gt;[n (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;range &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1 101&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;[(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt; _] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        [_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two things to notice here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;This does not match the function signature that our tests expect&lt;&#x2F;li&gt;
&lt;li&gt;Anyway this code snippet will fail because core.match is not in the classpath&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Instead of adding the dependency to deps.edn and restarting the repl, we can
download this dependency just for this session using add-lib.&lt;&#x2F;p&gt;
&lt;p&gt;From user namespace we can evaluate the following form to download the new
library to try it out:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;add-lib &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;org.clojure&#x2F;core.match)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point the repl will show all the depencencies being downloaded to
satisfy our request. Once done it will possible to require the new library
and use it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;require &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;[clojure.core.match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[match]])
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true false&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    [_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;]     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:case1
&lt;&#x2F;span&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt; _]    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:case2
&lt;&#x2F;span&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true false&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:case3&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;;; =&amp;gt; :case3
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Beautiful! We have added a new lib to our running repl, we have tested
it and we are ready to use it to solve our problem.&lt;&#x2F;p&gt;
&lt;p&gt;Here is the new fizzbuzz implementation using core.match&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizzbuzz &lt;&#x2F;span&gt;&lt;span&gt;[n]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;[(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt; _] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      [_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Does it work? Well there is a proper test suite so lets try it out…and it works!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;clojure-add-lib&#x2F;images&#x2F;fizz-match-2023-05-12_19-04.png&quot; alt=&quot;test-passing&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;It does not happen every day to want to try a new library when working on something
but at times I really really wanted to have a new dependency being added to a running
project, without losing the current state, context and focus. And it is extremely
simple, so give it a try!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;&#x2F;h2&gt;
&lt;p&gt;In this post I skimmed trough a lot a topics to focus on the add-lib
flow, so here are some resources worth reading if you want to dig deeper,
and I highly suggest so!&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;practical.li&#x2F;&quot;&gt;Praticalli&lt;&#x2F;a&gt; on hot reloading &lt;a href=&quot;https:&#x2F;&#x2F;practical.li&#x2F;clojure&#x2F;clojure-cli&#x2F;projects&#x2F;add-libraries&#x2F;#hotload-libraries&quot;&gt;libraries&lt;&#x2F;a&gt; and his &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;practicalli&#x2F;clojure-cli-config&quot;&gt;clojure-cli-config&lt;&#x2F;a&gt; project (plus tons of awesome documentation)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;seancorfield&#x2F;clj-new&quot;&gt;clj-new&lt;&#x2F;a&gt; to create new projects using Clojure cli, not specifically mentioned in the post but useful&lt;&#x2F;li&gt;
&lt;li&gt;core.match &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;core.match&#x2F;wiki&quot;&gt;docs&lt;&#x2F;a&gt;, in you want to dig deeper (highly suggested)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;discuss&quot;&gt;Discuss&lt;&#x2F;h2&gt;
&lt;p&gt;Looking forward your feedback!&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;focaskater&#x2F;status&#x2F;1657075756013322295?s=20&quot;&gt;Twitter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;dev.to&#x2F;fpsd&#x2F;clojure-bites-dynamically-add-depencencies-at-runtime-6di&quot;&gt;DEV&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;13frvz7&#x2F;clojure_bites_dynamically_add_depencencies_at&#x2F;?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;Reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.linkedin.com&#x2F;posts&#x2F;francesco-pischedda_clojure-bites-dynamically-add-depencencies-activity-7063040495156891648-caK_?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;Linkedin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Unrefined generic cheatsheet</title>
          <pubDate>Sat, 06 May 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/cheatsheet-harder-than-expected/</link>
          <guid>https://fpsd.codes/blog/cheatsheet-harder-than-expected/</guid>
          <description xml:base="https://fpsd.codes/blog/cheatsheet-harder-than-expected/">&lt;h1 id=&quot;project&quot;&gt;Project&lt;&#x2F;h1&gt;
&lt;p&gt;Project name: Unrefined&lt;&#x2F;p&gt;
&lt;p&gt;Project URL: &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;https:&#x2F;&#x2F;unrefined.one&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Project sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;Unrefined&#x27;s UX for estimations is centered on the estimation cheatsheet,
with the assumption that it would help engineers to breakdown tasks to
smaller items and assign a number to each of them.&lt;&#x2F;p&gt;
&lt;p&gt;A cheatsheet provides a list of breakdown items such as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Implementation&lt;&#x2F;li&gt;
&lt;li&gt;Testing&lt;&#x2F;li&gt;
&lt;li&gt;Migrations&lt;&#x2F;li&gt;
&lt;li&gt;Risk&lt;&#x2F;li&gt;
&lt;li&gt;...and more&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For each item there is a description of the possible actions for example,
considering implementation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Small change to an existing functionality: 1 story point&lt;&#x2F;li&gt;
&lt;li&gt;New endpoint for an existing domain model: 3 story points&lt;&#x2F;li&gt;
&lt;li&gt;Implement two or more domain models with their endpoints: 8 story points&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With the guidance of the cheatsheet, over time it will become easier and 
more predictable how to estimate a task, getting to consensus much faster.&lt;&#x2F;p&gt;
&lt;p&gt;One thing it does NOT is to make estimations more ACCURATE! Estimations are
gut feeling predictions of how much it could take to complete a task, and
should NOT be used to set deadlines.&lt;&#x2F;p&gt;
&lt;p&gt;Here is how it looks in Unrefined&#x27;s estimation section&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;blog&#x2F;cheatsheet-harder-than-expected&#x2F;images&#x2F;unrefined-cheatsheet.png&quot; alt=&quot;cheatsheet&quot; title=&quot;Default cheatsheet&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;The current implementation comes from our experience at $work so it is
very specific to our needs, making not so useful for other team in a 
different domain, with different tech stacks and so on.&lt;&#x2F;p&gt;
&lt;p&gt;For this reason I have started to abstract away the breakdown items and 
their description but it is clearly very hard to come up with something
that can capture every possible permutation of stacks, habits, business
domain and what not. This is a source of frustration because it makes
it harder to market the tool to other teams.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;going-forward&quot;&gt;Going forward&lt;&#x2F;h2&gt;
&lt;p&gt;I am not sure how to proceed with the cheatsheet but I have some ideas:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Create a generic one, knowing that it will not be a perfect fit for all cases&lt;&#x2F;li&gt;
&lt;li&gt;Start collecting feedback and create specialized cheatsheets&lt;&#x2F;li&gt;
&lt;li&gt;Give people the opportunity to create their own cheatsheet&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I think that all of the above options are viable and would help to make this
tool more useful to a broader audience, maybe even outside the software
engineering circle.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Clojure bites - clojure.test&#x2F;are</title>
          <pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/clojure-test-are/</link>
          <guid>https://fpsd.codes/blog/clojure-test-are/</guid>
          <description xml:base="https://fpsd.codes/blog/clojure-test-are/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Clojure&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.test&#x2F;are&quot;&gt;clojure.test&#x2F;are&lt;&#x2F;a&gt; provides a data driven approach to unit testing.
Lets start with a practical example, implementing the most important function
in the history of computer science, FizzBuzz! (But only because all of my
binary search trees are already balanced).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fizzbuzz&quot;&gt;FizzBuzz&lt;&#x2F;h2&gt;
&lt;p&gt;It is (was?) common that, during an interview, to be asked to implement the
logic of the FizzBuzz game, Wikipedia has a nice &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Fizz_buzz&quot;&gt;article&lt;&#x2F;a&gt; about it.&lt;&#x2F;p&gt;
&lt;p&gt;It can be summarized as follows:&lt;&#x2F;p&gt;
&lt;p&gt;Write a function that takes a numerical argument and returns:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The string Fizz if the number is divisible by 3&lt;&#x2F;li&gt;
&lt;li&gt;The string Buzz if the number is divisible by 5&lt;&#x2F;li&gt;
&lt;li&gt;The string FizzBuzz if the number is divisible by both 3 and 5&lt;&#x2F;li&gt;
&lt;li&gt;The argument if none of the previous conditions are met&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-implementation&quot;&gt;The implementation&lt;&#x2F;h2&gt;
&lt;p&gt;Lets start by defining the test suite using the usual clojure.test&#x2F;is macro.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; fizzbuzz.core-test
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[clojure.test &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[deftest is testing]]
&lt;&#x2F;span&gt;&lt;span&gt;              [fizzbuzz.core &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; sut]))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;deftest &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;OMG-FizzBuzz
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;testing &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Should return the numerical argument&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sut&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;testing &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Should return Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sut&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;testing &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Should return Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sut&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;testing &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Should return FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sut&#x2F;fizz-buzz &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;15&lt;&#x2F;span&gt;&lt;span&gt;))))
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Please note that sut stands for system under test; I&#x27;ve seen it being used
here and there but I am not sure it is a best practice or not.&lt;&#x2F;p&gt;
&lt;p&gt;The test will clearly fail because there is no fizz-buzz function or even a
fizzbuzz.core namespace. Lets start with a trivial implementation.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; fizzbuzz.core)
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizz-buzz &lt;&#x2F;span&gt;&lt;span&gt;[n]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cond
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;15&lt;&#x2F;span&gt;&lt;span&gt;)) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now all tests are passing, the interviewer is more than happy but you
want to show off your skills and ask to improve both code and tests&lt;&#x2F;p&gt;
&lt;h2 id=&quot;improvements&quot;&gt;Improvements&lt;&#x2F;h2&gt;
&lt;p&gt;First thing to notice is that if a number is not a multiple of 3 or 5 then
we run 4 divisions and return n. A slightly improvement can be the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; fizzbuzz.core)
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;defn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fizz-buzz
&lt;&#x2F;span&gt;&lt;span&gt;    [n]
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cond
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)) (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;)) &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:else&lt;&#x2F;span&gt;&lt;span&gt; n))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Test are passing so we are confident that the function is working as expected,
and it is a bit more performing! Yes, we are not solving the world&#x27;s energy
crisis but it is something.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;data-driven-tests&quot;&gt;Data driven tests&lt;&#x2F;h2&gt;
&lt;p&gt;Looking at the tests we can notice that we are calling the same function with
different input values and expecting a specific result. User of other testing
libraries, for example &lt;a href=&quot;https:&#x2F;&#x2F;docs.pytest.org&#x2F;en&#x2F;7.3.x&#x2F;&quot;&gt;Pytest&lt;&#x2F;a&gt; may be familiar with the &lt;a href=&quot;https:&#x2F;&#x2F;docs.pytest.org&#x2F;en&#x2F;7.3.x&#x2F;how-to&#x2F;parametrize.html#pytest-mark-parametrize&quot;&gt;parametrize&lt;&#x2F;a&gt; decorator
that takes tuples of data and calls the test case with that data as parameters.
In Clojure we can achieve that with clojure.test&#x2F;are macro, here is the docstring:&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;Checks multiple assertions with a template expression.
See clojure.template&#x2F;do-template for an explanation of
templates.&amp;quot;&lt;&#x2F;p&gt;
&lt;p&gt;A bit cryptic but an example can help us understand better.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ns&lt;&#x2F;span&gt;&lt;span&gt; fizzbuzz.core-test
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:require &lt;&#x2F;span&gt;&lt;span&gt;[clojure.test &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:refer &lt;&#x2F;span&gt;&lt;span&gt;[deftest are]]
&lt;&#x2F;span&gt;&lt;span&gt;              [fizzbuzz.core &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;:as&lt;&#x2F;span&gt;&lt;span&gt; sut]))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;deftest &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;OMG-FizzBuzz
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;are &lt;&#x2F;span&gt;&lt;span&gt;[argument expected] (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; expected (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sut&#x2F;fizz-buzz&lt;&#x2F;span&gt;&lt;span&gt; argument))
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1 1
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2 2
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3 &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;6 &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fizz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5 &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10 &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Buzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;15 &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;FizzBuzz&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And voila, we have a data driven test suite for our implementation!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;is-it-all-rainbows-and-unicorns&quot;&gt;Is it all rainbows and unicorns?&lt;&#x2F;h2&gt;
&lt;p&gt;clojure.test&#x2F;are comes with few shortcomings:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reporting: if one of the cases will fail, the report will not point directly to offending line but the line where are is called; usually it is not hard to get to the failing case anyway&lt;&#x2F;li&gt;
&lt;li&gt;It is easy to put a call to clojure.test&#x2F;is inside a call to &lt;em&gt;are&lt;&#x2F;em&gt;, this will make the pass anyway; generally speaking a new test case should fail at first and then we adjust it to make it pass, this should protect for the accidental use if &lt;em&gt;is&lt;&#x2F;em&gt; but it is something to keep in mind.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;&#x2F;h2&gt;
&lt;p&gt;clojure.test&#x2F;are is a builtin macro but it comes with some problems (not so big IMHO), so are there valuable alternatives? Some lovely people pointed them out in the comments
of the &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;Clojure&#x2F;comments&#x2F;135d5ak&#x2F;a_quick_intro_to_clojuretestare&#x2F;&quot;&gt;post&lt;&#x2F;a&gt; on Reddit:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;plain doseq: the rationale is that &lt;em&gt;are&lt;&#x2F;em&gt; introduce new syntax where &lt;em&gt;doseq&lt;&#x2F;em&gt; would do the same if massaged correctly&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;camsaul&#x2F;humane-are&quot;&gt;humane-are&lt;&#x2F;a&gt; a drop-in replacement for &lt;em&gt;are&lt;&#x2F;em&gt; with benefits such as better error reporting and bindings check (my personal favourite)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;I hope this will encourage exploring Clojure&#x27;s core library, to spot little
gems like this one, and to have added a new tool to your toolbox!&lt;&#x2F;p&gt;
&lt;p&gt;Code for this post can be found &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;8&#x2F;diffs&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Random bits</title>
          <pubDate>Thu, 27 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/random-bits/</link>
          <guid>https://fpsd.codes/blog/random-bits/</guid>
          <description xml:base="https://fpsd.codes/blog/random-bits/">&lt;h1 id=&quot;topics&quot;&gt;Topics&lt;&#x2F;h1&gt;
&lt;p&gt;This short post will cover:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;guile-commonmark bug&lt;&#x2F;li&gt;
&lt;li&gt;website next steps&lt;&#x2F;li&gt;
&lt;li&gt;OBS&lt;&#x2F;li&gt;
&lt;li&gt;experiment&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-bug-that-almost-got-me-out-of-haunt-static-site-generator&quot;&gt;The bug that almost got me out of Haunt static site generator&lt;&#x2F;h2&gt;
&lt;p&gt;At the time of the second post I was already super excited by the
project, the content was coming out nicely and wanted to have it 
out in the wild ASAP! As usual I launched &lt;code&gt;haunt build&lt;&#x2F;code&gt; command to
generate the static pages and all I got was an exception...my mood
went down pretty quickly.&lt;&#x2F;p&gt;
&lt;p&gt;I was sure the problem was on my side, probably something formatted
the wrong way but I could not make it to work. I was thinking it was
a bug in Haunt itself but after carefully inspecting the stack trace
I&#x27;ve tracked down the problem to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OrangeShark&#x2F;guile-commonmark&quot;&gt;guile-commonmark&lt;&#x2F;a&gt;,
for which I&#x27;ve opened an &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OrangeShark&#x2F;guile-commonmark&#x2F;issues&#x2F;22&quot;&gt;issue&lt;&#x2F;a&gt;,
hoping to get some feedback soon(ish). Unfortunately I&#x27;ve quickly realized
that the project is not actively developed, and decided to find a 
workaround for it. Even worse, the test suite is failing so it is not
easy to understand if I am breaking other places.&lt;&#x2F;p&gt;
&lt;p&gt;At some point I&#x27;ve identified the line that was breaking and commented
it, only because it was doing some cleanup, and having some extra 
spaces or unneeded HTML tags in the output is not the end of the world.&lt;&#x2F;p&gt;
&lt;p&gt;If curious or you are affected by the same problem, this is the line to
comment &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OrangeShark&#x2F;guile-commonmark&#x2F;blob&#x2F;master&#x2F;commonmark&#x2F;blocks.scm#L272&quot;&gt;link&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;p.s. I think this happens because I am compiling with Guile 3.0.x, maybe with 
Guile 2.x it works well...but I haven&#x27;t tested it yet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;website-next-steps&quot;&gt;Website next steps&lt;&#x2F;h2&gt;
&lt;p&gt;I want to keep the website features to the bare minimum but at the same
time there are some missing bits:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Links to contacts and social accounts&lt;&#x2F;li&gt;
&lt;li&gt;Project pages&lt;&#x2F;li&gt;
&lt;li&gt;Tags&lt;&#x2F;li&gt;
&lt;li&gt;Maybe a page for the CV?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I will slowly work on these parts, there is no rush but at least it is written
somewhere publicly and not only in my &amp;quot;secret&amp;quot; project files :) .&lt;&#x2F;p&gt;
&lt;h2 id=&quot;obs&quot;&gt;OBS&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve started learning to use OBS because I want to record a couple of videos to
explain how &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt; works and how the Chrome Extension
may improve the workflow.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve got a course on &lt;a href=&quot;https:&#x2F;&#x2F;www.udemy.com&#x2F;course&#x2F;start-streaming-with-obs-studio-in-2022&#x2F;&quot;&gt;Udemy&lt;&#x2F;a&gt;
and I am liking it a lot so far! (Not affiliated).&lt;&#x2F;p&gt;
&lt;p&gt;Frankly speaking, one day I would like to try to stream a coding session on one of
my projects to get live feedback and have some fun! I am an introvert and I am 
postponing this step every week, but at some point it will happen, so stay tuned!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-experiment&quot;&gt;The experiment&lt;&#x2F;h2&gt;
&lt;p&gt;This time I&#x27;ll limit the number of places where I&#x27;ll advertise my posts in order to
understand how much recurring traffic I have, if any. Clearly marketing helps a lot
but at times I feel I am a bit spammy and I am considering to promote fewer posts
in future. Lets see how it goes :) .&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Wave start - 2022-04-26</title>
          <pubDate>Wed, 26 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/wave-2022-04-26/</link>
          <guid>https://fpsd.codes/blog/wave-2022-04-26/</guid>
          <description xml:base="https://fpsd.codes/blog/wave-2022-04-26/">&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;&#x2F;h2&gt;
&lt;p&gt;In this wave (yes, yes, wave sound better than sprint) I&#x27;ll work to make some
progress on &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt; and, for the first time, I will
try to launch something new which I have in the back burner for some time.&lt;br &#x2F;&gt;
For the first time I will be trying to use the most suggested approach to build
a landing page and see if there is interest in the product, before jumping to the
code. It would be great to collect some pre-sales but I don&#x27;t know to deal with
them given that I don&#x27;t have a legal entity yet, and I don&#x27;t want to get in
troubles with the law...&lt;&#x2F;p&gt;
&lt;p&gt;Roughly I am going to spend my time on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Improving the estimation cheatsheet in Unrefined&lt;&#x2F;li&gt;
&lt;li&gt;Creating a landing page and marketing it (throwing some $$$ in marketing maybe)&lt;&#x2F;li&gt;
&lt;li&gt;Improving this website, there are still some information missing&lt;&#x2F;li&gt;
&lt;li&gt;Reveal the new experiment and first impressions&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;See you in the next update, and don&#x27;t forget to subscribe to the &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;rss-feed.xml&quot;&gt;RSS&lt;&#x2F;a&gt; feed!&lt;&#x2F;p&gt;
&lt;p&gt;For live updates track this &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;8&quot;&gt;MR&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Closing sprint 2022-04-17</title>
          <pubDate>Tue, 25 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/closing-sprint-2022-04-17/</link>
          <guid>https://fpsd.codes/blog/closing-sprint-2022-04-17/</guid>
          <description xml:base="https://fpsd.codes/blog/closing-sprint-2022-04-17/">&lt;p&gt;Project name: FPSD&lt;&#x2F;p&gt;
&lt;p&gt;Project URL: &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&quot;&gt;https:&#x2F;&#x2F;fpsd.codes&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Project sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sprint merge request: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;6&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;6&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-went&quot;&gt;How it went&lt;&#x2F;h2&gt;
&lt;p&gt;Sprint 2022-04-17 has been quite different from the first one, as I&#x27;ve already
figured out some details on how to run this web site I have spent more time 
on the next steps for my projects. This is a good outcome because I was worried
that I was spending too much time on marginal topics instead of making progress
on my projects.&lt;&#x2F;p&gt;
&lt;p&gt;Finally I have &lt;a href=&quot;&#x2F;announcing-unrefineds-chrome-extension.html&quot;&gt;released&lt;&#x2F;a&gt; the Chrome Extension
for &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt;; I was procrastinating this release for
too long and after mentioning it in a previous post I was feeling the pressure
I&#x27;ve put on myself which gave the energy boots to finally make it public.&lt;br &#x2F;&gt;
It is not perfect yet but at least I can start to get some feedback!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;numbers&quot;&gt;Numbers&lt;&#x2F;h2&gt;
&lt;p&gt;I was trying to get to 100 unique visitor before making this post but, honestly,
having reached 92 is already a good result for something started 15 days ago,
and basically no network!&lt;&#x2F;p&gt;
&lt;p&gt;Lets have a look at what Plausible says:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;plausible-2023-04-25_10-42.png&quot; alt=&quot;Plausible&quot; title=&quot;stats&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Key takeaways:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;views are still too bounded to my post on socials, no recurring traffic&lt;&#x2F;li&gt;
&lt;li&gt;longer posts have better performance&lt;&#x2F;li&gt;
&lt;li&gt;(Not visible from the graph) Still no engagement&lt;&#x2F;li&gt;
&lt;li&gt;Some sources are better than others, in order: Linkedin, Twitter, DevTo, fediverse&lt;&#x2F;li&gt;
&lt;li&gt;Copy&#x2F;Pasting the same post to all social account does not work well, especially on DevTo&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;There is still a lot to learn but I am enjoying the experience so far. Being an introvert
it makes it hard to push a bit more on the marketing side, but in the Internet
no one knows that you are a dog ;) .&lt;&#x2F;p&gt;
&lt;p&gt;One last thing, I will discontinue the term &amp;quot;sprint&amp;quot; in favor of wave; to me it feels
more in line with what I am doing and my mental processes.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Announcing Unrefined&#x27;s Chrome Extension</title>
          <pubDate>Mon, 24 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/announcing-chrome-extension/</link>
          <guid>https://fpsd.codes/blog/announcing-chrome-extension/</guid>
          <description xml:base="https://fpsd.codes/blog/announcing-chrome-extension/">&lt;h1 id=&quot;project&quot;&gt;Project&lt;&#x2F;h1&gt;
&lt;p&gt;Project name: Unrefined&lt;&#x2F;p&gt;
&lt;p&gt;Project URL: &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;https:&#x2F;&#x2F;unrefined.one&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Project sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Extension sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&#x2F;-&#x2F;tree&#x2F;main&#x2F;browser-extension&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&#x2F;-&#x2F;tree&#x2F;main&#x2F;browser-extension&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chrome-extension-for-ems-is-out&quot;&gt;Chrome Extension for EMs is out!&lt;&#x2F;h2&gt;
&lt;p&gt;After a bit of testing, the new Chrome Extension is out, targeting Unrefined&#x27;s
production environment!&lt;&#x2F;p&gt;
&lt;p&gt;What was stopping its release was that I had no build script in place to package
the extension, and asking to install it by pulling the repo and loading it
manually is a bit too much, even for Engineering Managers.&lt;&#x2F;p&gt;
&lt;p&gt;It is still not an &amp;quot;official&amp;quot; extension so, to work, it must be installed using
developer mode. I am planning to put it in the Extensions Store at some point but
better to wait until it will have a least few rounds of tests.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;developer.chrome.com&#x2F;docs&#x2F;extensions&#x2F;mv3&#x2F;getstarted&#x2F;development-basics&#x2F;#load-unpacked&quot;&gt;Here&lt;&#x2F;a&gt; is how to install an extension in developer mode.&lt;&#x2F;p&gt;
&lt;p&gt;You can get it from this &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&#x2F;assets&#x2F;chrome-extension&#x2F;unrefined.zip&quot;&gt;link&lt;&#x2F;a&gt;. It will be in Unrefined&#x27;s header after the next release.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;&#x2F;h2&gt;
&lt;p&gt;After installing it, a user can start and manage a refinement session directly
from the extension&#x27;s UI, without going to Unrefined web application.&lt;&#x2F;p&gt;
&lt;p&gt;Each time a new ticket is refined, a link to the estimation page is copied to
the clipboard so that the EM can share this link with the Engineers; actually
this is needed just the first time a refinement session is started, when
estimating new tickets, the estimation pages will update automatically, using
SSE. It may still be helpful to share the link again, in case someone else
will join an already started session, or if someone closes their browsers.&lt;&#x2F;p&gt;
&lt;p&gt;I am planning to record a couple of videos to show a session with and without
the use of the extension. (Never done that, it will be a mess for sure…)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;build-script&quot;&gt;Build script&lt;&#x2F;h2&gt;
&lt;p&gt;A few words about the build script; I needed something extremely simple to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;switch to production settings&lt;&#x2F;li&gt;
&lt;li&gt;package everything in a zip file, for easier distribution&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Another requirement for me was to try to not introduce other tools for such a
simple task.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately the web application is build using &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;guides&#x2F;tools_build&quot;&gt;tools.build&lt;&#x2F;a&gt; which is the
official, builtin, build tooling provided by Clojure, so the requirement
to not reach out to other tools has been satisfied!&lt;&#x2F;p&gt;
&lt;p&gt;It required literally few minutes to hack a target for the extension, which
does just what I need:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;clean previous build directory if any&lt;&#x2F;li&gt;
&lt;li&gt;copy the extension sources to a build directory&lt;&#x2F;li&gt;
&lt;li&gt;switch to prod settings&lt;&#x2F;li&gt;
&lt;li&gt;remove unneeded files&lt;&#x2F;li&gt;
&lt;li&gt;create a zip archive&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;clojure.github.io&#x2F;tools.build&#x2F;clojure.tools.build.api.html&quot;&gt;API&lt;&#x2F;a&gt; of tools.build library is simple and easy to reason about, plus there
are many example out there from which to get &amp;quot;inspiration&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Next steps, just QoL improvements for my release process:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;build the extension when building the application&#x27;s jar&lt;&#x2F;li&gt;
&lt;li&gt;copy new artifacts to the production environment&lt;&#x2F;li&gt;
&lt;li&gt;switch to the new version of the app and extension&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I am still not using any containerized environment and I am postponing this step
for when there will be a real benefit; so far my small VPS is more than enough
to run this project (and a couple of others BTW).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reference-change-set&quot;&gt;Reference change set&lt;&#x2F;h2&gt;
&lt;p&gt;To get an idea of the changes required for the build script head over to this
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&#x2F;-&#x2F;merge_requests&#x2F;43&quot;&gt;MR&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;I think that the Chrome Extension is a nice quality of life improvement for EMs
using Unrefined. I am eager to get feedback on it and work on its next iterations,
there is a lot of potential there!&lt;&#x2F;p&gt;
&lt;p&gt;Remember to subscribe to the &lt;a href=&quot;https:&#x2F;&#x2F;fpsd.codes&#x2F;rss-feed.xml&quot;&gt;RSS&lt;&#x2F;a&gt; feed to get updates on my progress, and stop by
to say &amp;quot;hello&amp;quot; on my social media accounts! Yes those are not linked here yet, but
chances are that you are getting to this page from my posts ;) &lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>New style using Terminal CSS</title>
          <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/new-style-with-terminal-css/</link>
          <guid>https://fpsd.codes/blog/new-style-with-terminal-css/</guid>
          <description xml:base="https://fpsd.codes/blog/new-style-with-terminal-css/">&lt;h1 id=&quot;from-0-to-star-struck&quot;&gt;From 0 to 🤩&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ve started this website not more than 10 days ago (at the time of this post), and&lt;br &#x2F;&gt;
since then it had no styling, just bare HTML, a list of posts and a link back to HOME.&lt;&#x2F;p&gt;
&lt;p&gt;While it was not a problem for me in general, at the same time I wanted to start putting&lt;br &#x2F;&gt;
some links here and there, for example in a nav-bar at the top.&lt;br &#x2F;&gt;
Instead of doing everything from scratch as I usually do, I decided to take a look around&lt;br &#x2F;&gt;
and from all the available options and usual suspects I&#x27;ve found &lt;a href=&quot;https:&#x2F;&#x2F;terminalcss.xyz&#x2F;&quot;&gt;Terminal CSS&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;What I like about it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;minimal styling&lt;&#x2F;li&gt;
&lt;li&gt;easy to implement, just inspect the elements on its website and you are ready to go!&lt;&#x2F;li&gt;
&lt;li&gt;very small size ~3k gzipped&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;From now on it will be my go to CSS framework, well done Terminal CSS team!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Updates on projects</title>
          <pubDate>Fri, 21 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/update-on-unrefined-roadmap/</link>
          <guid>https://fpsd.codes/blog/update-on-unrefined-roadmap/</guid>
          <description xml:base="https://fpsd.codes/blog/update-on-unrefined-roadmap/">&lt;h1 id=&quot;updates&quot;&gt;Updates&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;what-s-going-on&quot;&gt;What&#x27;s going on&lt;&#x2F;h2&gt;
&lt;p&gt;Hi there!&lt;&#x2F;p&gt;
&lt;p&gt;This sprint started in a different way, compared to the previous one.&lt;&#x2F;p&gt;
&lt;p&gt;One key difference is that I have published less content, the reason&lt;br &#x2F;&gt;
being that I&#x27;ve been busy at work and I haven&#x27;t been able to focus on&lt;br &#x2F;&gt;
new posts, even if I had a couple of ideas ready:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A bug in the guile-commonmark library used by Haunt and I have worked around it&lt;&#x2F;li&gt;
&lt;li&gt;Work on the site layout and describe how to achieve it with Haunt&lt;&#x2F;li&gt;
&lt;li&gt;Next steps for Unrefined&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The size of the posts would have been micro, small and medium respectively,&lt;br &#x2F;&gt;
so quite achievable compared to the previous week. So what happened? &lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-back-to-the-roots&quot;&gt;Getting back to the roots&lt;&#x2F;h2&gt;
&lt;p&gt;I have started this site to document the progress of my projects and I have realized&lt;br &#x2F;&gt;
that, excluding the site itself, I haven&#x27;t improved any other project so far, and&lt;br &#x2F;&gt;
not because there is not any work to do!&lt;&#x2F;p&gt;
&lt;p&gt;To be honest, it was expected that it requires some time to bootstrap a website,&lt;br &#x2F;&gt;
but at the same time content about the projects is king here, and I want to give&lt;br &#x2F;&gt;
it the top priority. In order to get more content about my progresses I have to&lt;br &#x2F;&gt;
actually work on the projects! :D&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unrefined-s-backlog-and-next-steps&quot;&gt;Unrefined&#x27;s backlog and next steps&lt;&#x2F;h2&gt;
&lt;p&gt;Today I had a chance to get back to Unrefined, filling up its backlog and&lt;br &#x2F;&gt;
prioritizing it based on the feedback I&#x27;ve received and the experience&lt;br &#x2F;&gt;
accumulated using it at work.&lt;&#x2F;p&gt;
&lt;p&gt;Following an extract of the project&#x27;s backlog:&lt;&#x2F;p&gt;
&lt;h2 id=&quot;browser-extension&quot;&gt;Browser extension&lt;&#x2F;h2&gt;
&lt;p&gt;Goal of the browser extension is to streamline the flow of the refinement&lt;br &#x2F;&gt;
session for both EMs and Engineers.
For EMs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;refine a ticket from the tickets page without moving to Unrefined home page&lt;&#x2F;li&gt;
&lt;li&gt;get links for estimations and updates&lt;&#x2F;li&gt;
&lt;li&gt;view estimation results&lt;&#x2F;li&gt;
&lt;li&gt;re estimate the current ticket&lt;&#x2F;li&gt;
&lt;li&gt;estimate a new ticket&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For Engineers:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;join an ongoing refinement session&lt;&#x2F;li&gt;
&lt;li&gt;estimate current ticket from the ticket&#x27;s page&lt;&#x2F;li&gt;
&lt;li&gt;jump to the next ticket&#x27;s page&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The EM user flow is already implemented it needs some testing and packaging&lt;br &#x2F;&gt;
the extension for production use. For now the extension works only on&lt;br &#x2F;&gt;
Google Chrome&#x2F;Chromium, other browsers will be addressed in future.&lt;&#x2F;p&gt;
&lt;p&gt;The Engineer user flow is yet to be implemented.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;priorities&quot;&gt;Priorities&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;TODO Implement a build system for the extension, package and distribute it&lt;&#x2F;p&gt;
&lt;p&gt;Breakdown of the build system:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Generates a zip archive with the production version of the extension&lt;&#x2F;li&gt;
&lt;li&gt;Generates a version of the extension that does not need developer mode&lt;&#x2F;li&gt;
&lt;li&gt;Publishes the extension to the store&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TODO Design the Engineer user flow&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TODO Implement the Engineer user flow&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TODO Launch and beta test&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;generic-estimation-cheat-sheet&quot;&gt;Generic estimation cheat sheet&lt;&#x2F;h2&gt;
&lt;p&gt;The estimation cheat sheet should help Engineers to breakdown the work needed&lt;br &#x2F;&gt;
for a ticket and assign to each item the appropriate story points. The sum of&lt;br &#x2F;&gt;
all items&#x27; estimations will give the story point for the ticket being refined.&lt;&#x2F;p&gt;
&lt;p&gt;Another benefit of the cheat sheet is that it creates consistency across estimations,&lt;br &#x2F;&gt;
in case of outliers it is easy to find out if someone over&#x2F;under estimated the&lt;br &#x2F;&gt;
effort and makes it possible to discuss the different views on the ticket being&lt;br &#x2F;&gt;
refined.&lt;&#x2F;p&gt;
&lt;p&gt;Consistency also helps Product teams to forecast the effort needed to implement&lt;br &#x2F;&gt;
new features by looking at past, similar, tickets&#x27; estimations.&lt;&#x2F;p&gt;
&lt;p&gt;Currently the cheat sheet is hard coded and specific to a team for which this tools&lt;br &#x2F;&gt;
has been built. Other team may need different entries and, thinking a bit further,&lt;br &#x2F;&gt;
could be applied out of the software engineering world.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;priorities-1&quot;&gt;Priorities&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;TODO Provide a second, more general, hard coded cheatsheet&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TODO Provide a way to customize the cheat sheet&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;coming-next&quot;&gt;Coming next&lt;&#x2F;h2&gt;
&lt;p&gt;There are few more entries in the backlog which are not well defined yet:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Admin panel: to at least have a view of what is going on in the web app&lt;&#x2F;li&gt;
&lt;li&gt;Organizations and users: there is no concept of user yet, everything is publicly accessible&lt;&#x2F;li&gt;
&lt;li&gt;Stats: once users and organizations will be in the system it might be valuable to have some stats, yet to be verified&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The plan is to apply the final touches to the browsers extension and later focus on&lt;br &#x2F;&gt;
the estimation cheat sheet which, according to the feedback, is one of the most valuable&lt;br &#x2F;&gt;
features of the whole system, together with the estimation flow.&lt;&#x2F;p&gt;
&lt;p&gt;I am tempted to try to work on the next features during a live stream. The idea is&lt;br &#x2F;&gt;
intriguing but at the same time terrifying, I&#x27;ve never streamed before so it could&lt;br &#x2F;&gt;
be a fun experience as well as a terrible one!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h2&gt;
&lt;p&gt;It has been a busy week, I have produced much less content than expected but at the same&lt;br &#x2F;&gt;
time I needed a break to think about how to structure this effort to build in public.&lt;&#x2F;p&gt;
&lt;p&gt;There are still many lessons to learn, one of which is how to use the limited time I have&lt;br &#x2F;&gt;
at hand for my side projects. I hope to find a way to make this experience enjoyable for&lt;br &#x2F;&gt;
everyone (me included ;) ).&lt;&#x2F;p&gt;
&lt;p&gt;Stay tuned for the next updates!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Who is using Unrefined?</title>
          <pubDate>Tue, 18 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/add-plausible-to-unrefined/</link>
          <guid>https://fpsd.codes/blog/add-plausible-to-unrefined/</guid>
          <description xml:base="https://fpsd.codes/blog/add-plausible-to-unrefined/">&lt;h1 id=&quot;who-is-using-unrefined&quot;&gt;Who is using Unrefined?&lt;&#x2F;h1&gt;
&lt;p&gt;Excluding my colleagues at my day job I don&#x27;t know!&lt;&#x2F;p&gt;
&lt;p&gt;Recently I have shown &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt; to the world but I have no idea if&lt;br &#x2F;&gt;
someone else is using it. Yes I have &amp;quot;some&amp;quot; logs but those are not really useful&lt;br &#x2F;&gt;
to track visits, more for debugging purposes.&lt;&#x2F;p&gt;
&lt;p&gt;Given that I am liking &lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&quot;&gt;Plausible&lt;&#x2F;a&gt; so much I have&lt;br &#x2F;&gt;
added their analytics script to &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;Unrefined&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As always, it is as easy as adding a script tag, &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&#x2F;-&#x2F;merge_requests&#x2F;42&quot;&gt;here&lt;&#x2F;a&gt; is the change.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s it for today, as always feel free to share feedback or just say hello :)&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>New sprint started!</title>
          <pubDate>Tue, 18 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/opening-2023-04-17-post/</link>
          <guid>https://fpsd.codes/blog/opening-2023-04-17-post/</guid>
          <description xml:base="https://fpsd.codes/blog/opening-2023-04-17-post/">&lt;h1 id=&quot;2023-04-18-a-new-cycle-of-post-is-starting&quot;&gt;[2023-04-18] A new cycle of post is starting&lt;&#x2F;h1&gt;
&lt;p&gt;I enjoyed my first week with this new blog!&lt;&#x2F;p&gt;
&lt;p&gt;The pace is almost right, I don&#x27;t feel overwhelmed by my expectations,&lt;br &#x2F;&gt;
and I think I can find the time to write a bit more.&lt;&#x2F;p&gt;
&lt;p&gt;Priority for now is to get back to Unerfined and finish some pending&lt;br &#x2F;&gt;
work regarding:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Analytics: add Plausible, yes I have other logs but are not nearly as useful&lt;&#x2F;li&gt;
&lt;li&gt;Improve the cheatsheet, it is too specific to my employer needs&lt;&#x2F;li&gt;
&lt;li&gt;Finish and release the browser extension, potentially targeting more browsers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;On the micro posts side I have not much at hand yet:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Few words on a bug in the guile-commonmark library&lt;&#x2F;li&gt;
&lt;li&gt;Updates on the site analytics and making them public&lt;&#x2F;li&gt;
&lt;li&gt;Adjustments to the site&#x27;s layout, linking to my social accounts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As expected, the source material will be written
in &lt;code&gt;docs&lt;&#x2F;code&gt; directory and tracked in a fresh new branch and MR so that I can receive comments
and suggestions, the MR is available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;6&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Feel free to stop by, even to just say hello :) .&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Closing first sprint</title>
          <pubDate>Mon, 17 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/closing-first-sprint/</link>
          <guid>https://fpsd.codes/blog/closing-first-sprint/</guid>
          <description xml:base="https://fpsd.codes/blog/closing-first-sprint/">&lt;h1 id=&quot;how-it-went&quot;&gt;How it went&lt;&#x2F;h1&gt;
&lt;p&gt;In this shot post I want to highlight what happened and first impressions about
the process.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-process&quot;&gt;The process&lt;&#x2F;h2&gt;
&lt;p&gt;The idea is to break the development of the website in cycles of one or two weeks.&lt;&#x2F;p&gt;
&lt;p&gt;During this time I&#x27;ve started collecting material about my projects, ideas for posts&lt;br &#x2F;&gt;
and setting up the development tools and flow.&lt;&#x2F;p&gt;
&lt;p&gt;Each cycle I should produce at least one big or medium sized post about a side project&lt;br &#x2F;&gt;
and at the same time I should write micro posts to add new material to the site and&lt;br &#x2F;&gt;
get more visits.&lt;&#x2F;p&gt;
&lt;p&gt;Micro posts worked really well to keep the creative flow and attracting new visits&lt;br &#x2F;&gt;
to the site. Main topic of the micro posts was Haunt, the static website generator&lt;br &#x2F;&gt;
and I suspect that talking about tools in this bite sized format works really well.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-went-1&quot;&gt;How it went&lt;&#x2F;h2&gt;
&lt;p&gt;I would say not bad for a new website with no audience other than my personal network&lt;br &#x2F;&gt;
in the various social platforms, I may also have acquired a couple new followers,&lt;br &#x2F;&gt;
which is really nice!&lt;&#x2F;p&gt;
&lt;p&gt;Few stats:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;total unique visitors: 36 (39 - my views from different browsers)&lt;&#x2F;li&gt;
&lt;li&gt;average visitors per day: 6&lt;&#x2F;li&gt;
&lt;li&gt;unique visitors for the longer sized post: 20&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I would say that the site acquired 5 new visitors each day which is good, at least for&lt;br &#x2F;&gt;
my little experience running a site, not really a scientific analysis but enough for&lt;br &#x2F;&gt;
this stage.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next&quot;&gt;Next&lt;&#x2F;h2&gt;
&lt;p&gt;This post closes the first sprint started the 2023-04-09.&lt;&#x2F;p&gt;
&lt;p&gt;It took a little longer a week, I was expecting it take a bit longer, and&lt;br &#x2F;&gt;
probably for deep dives and more content rich posts it will take that much.&lt;br &#x2F;&gt;
For this reason I am not setting an hard deadline for each sprint, at least&lt;br &#x2F;&gt;
until I&#x27;ll find a reasonable benefit by doing so.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll start the new sprint (2023-04-17) later today.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Introducing Unrefined</title>
          <pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate>
          <author>FPSD</author>
          <link>https://fpsd.codes/blog/introducing-unrefined/</link>
          <guid>https://fpsd.codes/blog/introducing-unrefined/</guid>
          <description xml:base="https://fpsd.codes/blog/introducing-unrefined/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;Project name: Unrefined&lt;&#x2F;p&gt;
&lt;p&gt;Project URL: &lt;a href=&quot;https:&#x2F;&#x2F;unrefined.one&quot;&gt;https:&#x2F;&#x2F;unrefined.one&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Project sources: &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&quot;&gt;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-this-about&quot;&gt;What is this about&lt;&#x2F;h2&gt;
&lt;p&gt;Unrefined aims to help remote software engineering teams to run ticket refinement
and estimation sessions.&lt;&#x2F;p&gt;
&lt;p&gt;Refinement sessions are usually attended by:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;PMs: presenting their tickets and looking for estimations to plan next sprints&lt;&#x2F;li&gt;
&lt;li&gt;Engineers: refining tickets and providing estimations for them&lt;&#x2F;li&gt;
&lt;li&gt;EMs: coordinating the whole session&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Unrefined mainly targets EMs who coordinate the refinement and estimation sessions,
and Engineers who are in charge to break down the ticket in its actionable parts
and give an estimation of the effort needed to implement them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve started Unrefined essentially to scratch my own itch. At work we tried different
approaches and all of them never worked properly. Only one was really cool, a Zoom
bot developed by one of my colleagues, but at some point we left Zoom and I missed it
a lot. After that we used to cast our estimations in the chat of the video conferencing
tool we switched to. And that had few problems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Estimation bias: sometimes people wait for other votes to express their own&lt;&#x2F;li&gt;
&lt;li&gt;A bit hard to coordinate and to find out the final estimation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Wh I liked about the bot:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Everyone&#x27;s estimation was secret so there was no bias&lt;&#x2F;li&gt;
&lt;li&gt;The bot spitted out the final estimation or pointed out if more discussion was needed&lt;&#x2F;li&gt;
&lt;li&gt;Easy to use&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;development-process&quot;&gt;Development process&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;initial-work&quot;&gt;Initial work&lt;&#x2F;h3&gt;
&lt;p&gt;Based on the features provided by the bot, I have started thinking about how to structure a
web app or an api to manage these sessions, the number of features started growing
without even thinking at the core logic: provide a way to express estimations and give
out a result (either an estimation or a suggestion to discuss more).&lt;&#x2F;p&gt;
&lt;p&gt;At this point I&#x27;ve decided to scratch all of the unneeded features and started to implement
the core logic, without even thinking about how people will interact with it.&lt;&#x2F;p&gt;
&lt;p&gt;I have opted for &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;&quot;&gt;Clojure&lt;&#x2F;a&gt; because I like its interactive,
iterative development flow, being able to shape the program while it is running,
getting a taste of how the API would be used while using it.
There are many more reason to choose Clojure for your projects but this is not the right place to talk about that.&lt;&#x2F;p&gt;
&lt;p&gt;During a lazy Sunday I have quickly sketched out the core, which can be seen at this
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&#x2F;-&#x2F;commit&#x2F;ea410a5c0c9bddd5c805e5ad054a44fcf2b8f947&quot;&gt;commit&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
(Yeah, the commits are almost always a mess…but I&#x27;ll get disciplined later)&lt;&#x2F;p&gt;
&lt;p&gt;At that point I was so excited by its simplicity that I wanted to provide a way
to actually use it!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;making-it-usable&quot;&gt;Making it usable&lt;&#x2F;h3&gt;
&lt;p&gt;Having built the foundations it was time to think about how people would use it.&lt;&#x2F;p&gt;
&lt;p&gt;Goals:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to access and use, people should be able to create and join sessions with the least effort possible&lt;&#x2F;li&gt;
&lt;li&gt;Easily get to the ticket details by linking to the team&#x2F;company ticketing system&lt;&#x2F;li&gt;
&lt;li&gt;Avoid estimation bias by hiding all estimations until the final result is shown&lt;&#x2F;li&gt;
&lt;li&gt;Instantly get the estimation result&lt;&#x2F;li&gt;
&lt;li&gt;Easily estimate a new ticket if any&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This is the flow that I came up with:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;EM puts the ticket URL in the home page form field&lt;&#x2F;li&gt;
&lt;li&gt;EM gets the estimation link for engineers and shares it&lt;&#x2F;li&gt;
&lt;li&gt;Engineers discuss the ticket and express their estimation in the estimation page&lt;&#x2F;li&gt;
&lt;li&gt;EM + Engineers get realtime updates of the estimations currently expressed (now there is no bias)&lt;&#x2F;li&gt;
&lt;li&gt;EM gets the final estimation, updates the ticket with the resulting story points&lt;&#x2F;li&gt;
&lt;li&gt;EM starts a new estimation for the next ticket if any&lt;&#x2F;li&gt;
&lt;li&gt;Engineers browsers will update automatically to the next estimation page&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This flow stayed the same since the beginning and it worked quite well, at least for our needs at work.&lt;&#x2F;p&gt;
&lt;p&gt;One feature that came after the first draft is the estimation cheatsheet.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;estimation-cheatsheet&quot;&gt;Estimation cheatsheet&lt;&#x2F;h3&gt;
&lt;p&gt;Our estimation cheatsheet is a guideline that helps everyone to stay in the same page
when expressing the estimation and improve coherence, but this does not mean that
estimations are always equal because someone may include effort not considered by others like test, migrations and so on.&lt;&#x2F;p&gt;
&lt;p&gt;How the estimation looks like? I proposes a breakdown of activities, for example:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;implementation: code changes from small ones like adding a field to a response JSON or bigger ones like adding a new endpoint&lt;&#x2F;li&gt;
&lt;li&gt;automated tests: how many test should be added? If there is some tech debt test may require more effort&lt;&#x2F;li&gt;
&lt;li&gt;database migrations: are we just adding&#x2F;removing one field? Is a data migration needed?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;…and some more entries, but you get the point.&lt;&#x2F;p&gt;
&lt;p&gt;Adding the cheatsheet to the estimation page helped a lot to have more predictable and
coherent estimations. This is helpful for the product team too because, by looking at
past tickets they my have a better idea of the effort required by a new feature.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;moving-forward&quot;&gt;Moving forward&lt;&#x2F;h3&gt;
&lt;p&gt;Unrefined is now actively used at my workplace and I am proud of the outcome and I think
it could be implemented in other organizations. But before that I want to generalize the
estimation cheatsheet and provide a way to create a custom one.&lt;&#x2F;p&gt;
&lt;p&gt;Another feature I have in progress is the browser extension to help EMs to run estimation session. The extension is almost there but it needs some more testing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;next-steps-0-3&quot;&gt;Next steps [0&#x2F;3]&lt;&#x2F;h3&gt;
&lt;p&gt;1 TODO improve the cheatsheet management&lt;&#x2F;p&gt;
&lt;p&gt;2 TODO finish the browser extension, test it and port it to more browsers&lt;&#x2F;p&gt;
&lt;p&gt;3 TODO try to get feedback from other organizations or engineering teams&lt;&#x2F;p&gt;
&lt;h3 id=&quot;feedback-please&quot;&gt;Feedback please&lt;&#x2F;h3&gt;
&lt;p&gt;In case you want to share some feedback please come over to the project &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;unrefined&quot;&gt;repo&lt;&#x2F;a&gt;, it is open to everyone! &lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Customizing Haunt&#x27;s base layout</title>
          <pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/change-haunt-base-layout-to-load-plausible/</link>
          <guid>https://fpsd.codes/blog/change-haunt-base-layout-to-load-plausible/</guid>
          <description xml:base="https://fpsd.codes/blog/change-haunt-base-layout-to-load-plausible/">&lt;h1 id=&quot;2023-04-12-learning-how-haunt-s-theme-layout-system-works&quot;&gt;[2023-04-12] Learning how Haunt&#x27;s theme&#x2F;layout system works&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;i-just-want-to-embed-a-script&quot;&gt;I just want to embed a script!&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;adding-analytics.html&quot;&gt;Yesterday&lt;&#x2F;a&gt; I&#x27;ve integrated &lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&quot;&gt;plausible.io&lt;&#x2F;a&gt;
into the website. The process is extremely simple, like in most cases it is as easy
as adding a script tag to embed the tracking code and you are done.&lt;&#x2F;p&gt;
&lt;p&gt;Most static site generators usually have a set of template HTML files that are
filled with posts index, post contents and other stuff, but Haunt is a bit different,
as it provides its own template language that in turn will generate the HTML code.
The DSL reminds me a bit &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;hiccup&quot;&gt;Hiccup&lt;&#x2F;a&gt;, just replace
vectors with lists and keywords with symbols and the two are basically the same...if we 
don&#x27;t focus too much on the details.&lt;&#x2F;p&gt;
&lt;p&gt;This is the first time I am using Haunt and I had to do a bit of digging to figure out
how to add the tracking script. Fortunately there are plenty of examples, starting
from the project&#x27;s documentation &lt;a href=&quot;https:&#x2F;&#x2F;git.dthompson.us&#x2F;haunt.git&#x2F;tree&#x2F;website&quot;&gt;sources&lt;&#x2F;a&gt;,
where it is possible to see how the author is &lt;a href=&quot;https:&#x2F;&#x2F;git.dthompson.us&#x2F;haunt.git&#x2F;tree&#x2F;website&#x2F;haunt.scm#n79&quot;&gt;embedding&lt;&#x2F;a&gt;
piwik analytics in the project&#x27;s website.&lt;&#x2F;p&gt;
&lt;p&gt;Other site built with Haunt (together with sources) are listed &lt;a href=&quot;https:&#x2F;&#x2F;awesome.haunt.page&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;site-configuration-file&quot;&gt;Site configuration file&lt;&#x2F;h2&gt;
&lt;p&gt;Haunt expects a configuration file called haunt.scm, with the settings of the site like
domain name, site title, where to find the source posts etc in order to be able to build the
final site. Part of the settings are which readers to use, for example &lt;a href=&quot;https:&#x2F;&#x2F;commonmark.org&#x2F;&quot;&gt;CommonMark&lt;&#x2F;a&gt;,
or builders such as blog, feeds, static assets etc.&lt;&#x2F;p&gt;
&lt;p&gt;Each builder is customizable and the &lt;code&gt;blog&lt;&#x2F;code&gt; builder accept a &lt;code&gt;#:theme&lt;&#x2F;code&gt; parameter where it is
possible to provide a custom theme; when not specified the builtin
&lt;a href=&quot;https:&#x2F;&#x2F;git.dthompson.us&#x2F;haunt.git&#x2F;tree&#x2F;haunt&#x2F;builder&#x2F;blog.scm#n126&quot;&gt;ugly-theme&lt;&#x2F;a&gt; is used.&lt;&#x2F;p&gt;
&lt;p&gt;The theme is declared using the &lt;code&gt;theme&lt;&#x2F;code&gt; function that accepts the following parameters:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;name: name of the theme.&lt;&#x2F;li&gt;
&lt;li&gt;layout: a function that generates the HTML layout of the site.&lt;&#x2F;li&gt;
&lt;li&gt;post-template: a function that generates the HTML template of a single post.&lt;&#x2F;li&gt;
&lt;li&gt;collection-template: a function that returns the HTML template to represent a list of posts.&lt;&#x2F;li&gt;
&lt;li&gt;pagination-template: a function that returns the HTML template used for the pagination of posts.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Fortunately each parameter is optional so, to just customize the general layout to
include the script for the tracking code, it is enough to create a new function,
overriding the base layout function, to include the custom code and that&#x27;s it! Ez Pz&lt;&#x2F;p&gt;
&lt;p&gt;Here is my current custom theme in all its glory&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;(define (fpsd-default-layout site title body)
&lt;&#x2F;span&gt;&lt;span&gt;  `((doctype &amp;quot;html&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    (head
&lt;&#x2F;span&gt;&lt;span&gt;     (meta (@ (charset &amp;quot;utf-8&amp;quot;)))
&lt;&#x2F;span&gt;&lt;span&gt;     (script (@ (type &amp;quot;text&#x2F;javascript&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;		(defer &amp;quot;true&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;		(data-domain &amp;quot;fpsd.codes&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;		(src &amp;quot;https:&#x2F;&#x2F;plausible.io&#x2F;js&#x2F;script.js&amp;quot;)))
&lt;&#x2F;span&gt;&lt;span&gt;     (title ,(string-append title &amp;quot; — &amp;quot; (site-title site))))
&lt;&#x2F;span&gt;&lt;span&gt;    (body
&lt;&#x2F;span&gt;&lt;span&gt;     (h1 ,(site-title site))
&lt;&#x2F;span&gt;&lt;span&gt;     ,body)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(define fpsd-theme
&lt;&#x2F;span&gt;&lt;span&gt;  (theme #:name &amp;quot;FPSD&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;         #:layout fpsd-default-layout)) ;; &amp;lt;- please notice that I have defined only the layout
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;It is entertaining to play with new tools, sometimes we expect them to work in a certain
way and it can be frustrating if they don&#x27;t but, if we spend enough time trying to
get to understand them, then we can learn something new and even have some fun time!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Adding analytic</title>
          <pubDate>Tue, 11 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/add-analytics/</link>
          <guid>https://fpsd.codes/blog/add-analytics/</guid>
          <description xml:base="https://fpsd.codes/blog/add-analytics/">&lt;h1 id=&quot;2023-04-11-i-need-analytics&quot;&gt;[2023-04-11] I need analytics&lt;&#x2F;h1&gt;
&lt;p&gt;What is the point to build in public if I have no idea if my projects,
including this website, are getting traction?&lt;&#x2F;p&gt;
&lt;p&gt;I have no idea of the best practices, tools and whatever is needed to
keep track of traffic, conversion rates, retention and all the rest
so I have no clear idea to what look for, I guess I will learn along
the way.&lt;&#x2F;p&gt;
&lt;p&gt;Looking around for tools I have decided to try &lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&quot;&gt;plausible.io&lt;&#x2F;a&gt;
because it is simple and has an eye on user&#x27;s privacy which is a good
bonus to have.&lt;&#x2F;p&gt;
&lt;p&gt;It offers an on premise and a hosted solution which is reasonably priced;
even if at first I was going to try the self hosted option, at 9 EUR&#x2F;Month
it is not a huge investment and I can get started right away, so lets click
the &amp;quot;Start free trial&amp;quot; button and see what happens.&lt;&#x2F;p&gt;
&lt;p&gt;Another neat feature is that the dashboard can be publicly accessible, I am
considering opening them starting with the website analytics, at least it
looks intriguing.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Starting work on next post</title>
          <pubDate>Sun, 09 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/opening-2023-04-09-post/</link>
          <guid>https://fpsd.codes/blog/opening-2023-04-09-post/</guid>
          <description xml:base="https://fpsd.codes/blog/opening-2023-04-09-post/">&lt;h1 id=&quot;2023-04-09-started-collecting-material-for-a-new-blog-post&quot;&gt;[2023-04-09] Started collecting material for a new blog post&lt;&#x2F;h1&gt;
&lt;p&gt;According to my plans I am supposed to create a blog post announcing that I am starting
to work on a new post, so here we are.&lt;&#x2F;p&gt;
&lt;p&gt;As expected, the source material will be written
in &lt;code&gt;docs&lt;&#x2F;code&gt; directory and tracked in a fresh new branch and MR so that I can receive comments
and suggestions, the MR is available &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;merge_requests&#x2F;1&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Feel free to stop by, even to just say hello :) .&lt;&#x2F;p&gt;
&lt;p&gt;The next post should be a presentation of the projects I am working on but I am not sure 
if I will focus on a single project or if the post will be an overview of what is 
currently in the pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;Another aspect that I want to tune is how much I should say in the sprint start post,
for today I am keeping it simple.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Hello, World!</title>
          <pubDate>Sat, 08 Apr 2023 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://fpsd.codes/blog/intro/</link>
          <guid>https://fpsd.codes/blog/intro/</guid>
          <description xml:base="https://fpsd.codes/blog/intro/">&lt;h1 id=&quot;hello-world&quot;&gt;Hello, World!&lt;&#x2F;h1&gt;
&lt;p&gt;My name is Francesco, sometimes you can find me by the handle &lt;code&gt;fpsd&lt;&#x2F;code&gt; around the 
net, sometimes with other handles but probably used for others purposes.&lt;&#x2F;p&gt;
&lt;p&gt;In this website I will collect brain dumps around programming, software&#x2F;product
development or any other topic I&#x27;ll find worth sharing, even if, sometimes, not
so worth reading ;).&lt;&#x2F;p&gt;
&lt;p&gt;Soon I&#x27;ll share my goals and expectations for this project, together with its 
development plan, so stay tuned!&lt;&#x2F;p&gt;
&lt;p&gt;This site was generated by the awesome, hackable, site generator
&lt;a href=&quot;https:&#x2F;&#x2F;dthompson.us&#x2F;projects&#x2F;haunt.html&quot;&gt;Haunt&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;But now I have switched to the equally awesome &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt;!
More on this change in another post, I promise!&lt;&#x2F;p&gt;
&lt;p&gt;p.s. as a sneak pick, head to the project &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;fpischedda&#x2F;fp-site&#x2F;-&#x2F;tree&#x2F;main&#x2F;docs&#x2F;intro.org&quot;&gt;docs&lt;&#x2F;a&gt;,
this should give you an idea of what I have in mind.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
