<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Wojciech Krzywiec on Medium]]></title>
        <description><![CDATA[Stories by Wojciech Krzywiec on Medium]]></description>
        <link>https://medium.com/@wkrzywiec?source=rss-8fb6f1a94f5b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*wdAKzswPu9cIOqfE-EmO-w.jpeg</url>
            <title>Stories by Wojciech Krzywiec on Medium</title>
            <link>https://medium.com/@wkrzywiec?source=rss-8fb6f1a94f5b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 12:52:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@wkrzywiec/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Building AI-Powered Software: Streaming responses]]></title>
            <link>https://wkrzywiec.medium.com/building-ai-powered-software-streaming-responses-433f328c1554?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/433f328c1554</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[streaming]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Sun, 06 Sep 2026 00:00:34 GMT</pubDate>
            <atom:updated>2026-09-13T12:58:38.694Z</atom:updated>
            <content:encoded><![CDATA[<p><em>This is the second part of the “Building AI-Powered Software” which focuses on improving the app’s core user experience by returning small pieces of the response quickly. That way, users won’t wonder whether the app has crashed or needs more time, because they receive constant feedback that work is progressing behind the scenes.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*g1VsFLqG9RIcEjYI.jpg" /></figure><h3>Why is it taking so long? Did it crash?</h3><p>In my previous article in this series (here is a <a href="https://medium.com/@wkrzywiec/building-ai-powered-software-model-embeddings-da8d040166c9">link</a>) there was presented a simple endpoint that returns a list of recipes curated by an LLM based on user input. The result is a nicely structured, but to get it we sometimes have to wait a couple of seconds. Generating a response involves several slower steps, like embedding the user input or waiting for an LLM response. The more complicated the process, the longer the user may wait for the final result. For very complex processes it may even taken hours!</p><p>Here is how it looks now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*agiGsOjj7DSdYswC.gif" /></figure><p>As you can see, the entire response is returned only after the whole process completes. In the meantime there is no quick feedback to the user about what’s happening, so they may think the application has crashed.</p><p>It is better to notify the user about what’s happening — something like “Hey, we got your input and we’re working on it,” followed by “We found some delicious recipes; we’ll show the best ones in a moment”. Moreover if a task is to return multiple items (e.g. list of recipes, rationale, suggested next action) it is good return them once they are produced. There is no need to wait untill all of them are finished. This avoids the impression that the app is stuck.</p><h3>Streaming chunks</h3><p>When interacting with most LLM products (ChatGPT, Gemini, etc.), we may notice that responses are returned to us look like they were typed. It’s not just a cosmetic effect, it’s an optimization that informs the user of partial results as soon as they’re available, without waiting for the full response.</p><p>There are several solutions to implement streaming chunks (sending small pieces of the entire response). Many variations exist, but they all boil down to two major decisions:</p><ul><li>which communication protocol to choose,</li><li>how to structure each chunk.</li></ul><h4>Protocols</h4><p>The first decision we have to make is which communication protocol to select, or in other words, how we’d like to stream chunks from server to client.</p><p>Options we have:</p><ul><li>standard <strong>HTTP</strong> — which can be realized with the following mechanisms: <strong>Server-Sent Events </strong>(SSE) &amp;<strong> Newline Delimited JSON</strong> (NDJSON)</li><li><strong>Websockets</strong></li></ul><p>All approaches allow a long-lived connection with the server and can send messages in chunks.</p><p>We could also consider <strong>gRPC</strong>, but its strengths shine in cross-agent communication within multi-agent systems, when one agent interacts with another. Therefore I’ll skip it for this post.</p><h4>Server-Sent Events</h4><p>Server-Sent Events is a mechanism of unidirectional communication between server and client. That means only the server can send data to the client; the opposite direction is not supported.</p><p>To receive an SSE stream the client first needs to make a standard HTTP request with the Accept: text/event-stream header, after which an open connection is established. It will persist until one side (client or server) closes it. During the connection the server pushes messages in plain text; these can be JSON, but they don&#39;t have to be.</p><p>Technically there are no constraints on how messages are structured. However, conventions most clients and servers follow include:</p><ul><li>a double line (&quot;\n\n&quot;) between each message/chunk,</li><li>a message in the format data: &lt;message&gt;,</li><li>an optional event: &lt;event type&gt; field that describes the event (like add, remove, or business-centric types like addedToCart),</li><li>an optional id: &lt;event id&gt; field with a message identifier</li></ul><p>So the response, with two events, may look like this:</p><pre>id: 1 <br>event: start <br>data: Hello <br><br><br>id: 2 <br>event: add <br>data: there!</pre><p>SSE is currently the most popular mechanism for AI chats because OpenAI uses it in their streaming API. OpenAI was the first widely used LLM chat provider, and many companies and tools adopted SSE as a result — it has become the de facto standard in the AI chat industry.</p><p>Therefore SSE is a good option if you want to integrate your app with popular chat UIs, like <a href="https://openwebui.com/">Open WebUI</a>, <a href="https://chainlit.io/">Chainlit</a>, <a href="https://ollama.com/">Ollama Desktop App</a> or <a href="https://www.jan.ai/">Jan.ai</a>.</p><h4>NDJSON</h4><p>NDJSON stands for Newline Delimited JSON. It’s a format where multiple JSON objects are separated by newline characters (\n). Each line is a valid JSON object and can be treated as a sequence of separate objects/events. This simple format is close to a single JSON array, which makes it easier to integrate with existing tools.</p><p>Like SSE, NDJSON relies on an HTTP long-lived connection in which each line (a JSON object) is sent. Unlike SSE, the NDJSON format enforces structured data, which can make it easier to maintain and integrate.</p><p>For example, here is how an exemplary response could look (the Content-Type HTTP header would be application/x-ndjson):</p><pre>{ &quot;id&quot;: 1, &quot;event&quot;: &quot;start&quot;, &quot;data&quot;: &quot;Hello&quot;}<br>{ &quot;id&quot;: 2, &quot;event&quot;: &quot;add&quot;, &quot;data&quot;: &quot;there!&quot;}</pre><p>The NDJSON format is a bit of a niche but it’s used in some systems like Ollama and can be an alternative to SSE.</p><h4>Websockets</h4><p>Sometimes one-way communication (server to client only) is too limiting. For instance, systems where an AI agent needs to interact with a human (e.g., to ask for permission or to review plan/task progress — human-in-the-loop) require two-way communication. Such systems benefit from long-lived, bi-directional connections, which avoid a flood of HTTP requests for a single communication channel. WebSocket is useful not only for chats but also for many real-time collaboration systems like <em>Miro</em> or <em>Figma</em>.</p><p>Both HTTP and WebSockets are built on the same foundation — TCP (Transmission Control Protocol). WebSocket communication starts with an HTTP handshake in which the client asks the server to upgrade the connection to a long-lived WebSocket. If the server accepts, the same TCP channel remains open and is upgraded to a WebSocket connection; it lasts until either side closes it. In standard HTTP, the client opens a TCP connection, sends a request, and the connection is often closed after the response.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/887/1*OdKRCluwGV3Y7MUom484VA.png" /></figure><p>WebSocket supports various data types; the most useful for us is the text type, which allows us to send structured messages (for example JSON) back and forth between client and server. Here is an example of message exchange for a chat application, with messages in JSON format.</p><p>In the AI-agent world, WebSockets are used in various products. For example, Perplexity uses WebSockets to give completion suggestions while the user is typing — when I hit a letter, Perplexity often already suggests what I might want to type.</p><pre>// Client -&gt; Server<br>{&quot;type&quot;:&quot;join&quot;, &quot;room&quot;:&quot;general&quot;, &quot;user&quot;:&quot;wojtek&quot;}<br><br>// Client -&gt; Server<br>{&quot;type&quot;:&quot;chat&quot;, &quot;room&quot;:&quot;general&quot;, &quot;user&quot;:&quot;wojtek&quot;, &quot;message&quot;:&quot;hello everyone&quot;}<br><br>// Server -&gt; Client<br>{&quot;type&quot;:&quot;chat&quot;, &quot;room&quot;:&quot;general&quot;, &quot;user&quot;:&quot;alice&quot;, &quot;message&quot;:&quot;hello Wojtek&quot;, &quot;timestamp&quot;:&quot;2026-05-12T10:15:00Z&quot;}<br><br>// Server -&gt; Client<br>{&quot;type&quot;:&quot;ping&quot;}<br><br>// Client -&gt; Server<br>{&quot;type&quot;:&quot;pong&quot;}</pre><p>The ping/ pong messages are heartbeats - a simple mechanism for client and server to ensure the connection is still alive. The others are the regular messages exchanged between client and server.</p><h3>Shaping response structure</h3><p>Once a transport mechanism is chosen, there is one more decision: do we want to send structured responses from the agent? For a simple chat application, markdown may be sufficient. But if you want UI elements (cards, animations, or other components) you need a structured one.</p><p>In essence, this is similar to the standard approach: define a response schema (for example in OpenAPI) and the server returns the result. The trick here is how to stream such an object. In most applications the server returns the full JSON object at once; in an agentic application we may want to send only small parts of a large JSON as the LLM produces them.</p><p>But first, let’s visualize it with an example. Say the final response for a <strong><em>nutri-chef-ai</em></strong> agent looks like this:</p><pre>{<br>    &quot;response&quot;: &quot;Thank you for your meal planning request for healthy and fulfilling meals.&quot;,<br>    &quot;suggestedFollowUps&quot;: [<br>        &quot;Prepare a shopping list for the ingredients needed for the selected recipes.&quot;<br>    ],<br>    &quot;recipes&quot;: [<br>        {<br>            &quot;recipe&quot;: {<br>                &quot;id&quot;: &quot;6464b6f5-17bf-4744-90f6-dbaab3af9983&quot;,<br>                &quot;name&quot;: &quot;Mexican Quinoa&quot;,<br>                &quot;description&quot;: &quot;...&quot;,<br>                &quot;ingredients&quot;: [<br>                    {<br>                        &quot;section&quot;: &quot;all&quot;,<br>                        &quot;ingredients&quot;: [...]<br>                    }<br>                ],<br>                &quot;instructions&quot;: [...],<br>                &quot;sourceUrl&quot;: &quot;https://www.bbcgoodfood.com/&quot;,<br>                &quot;source&quot;: &quot;bbc_good_food&quot;,<br>                &quot;imageUrl&quot;: &quot;https://www.bbcgoodfood.com/quinoa.jpg&quot;,<br>                &quot;servings&quot;: &quot;2 portions&quot;,<br>                &quot;tags&quot;: [<br>                    &quot;Lunch&quot;<br>                ],<br>                &quot;similarityScore&quot;: 0.6430065<br>            }<br>        }<br>    ]<br>}</pre><p>This is a large JSON with a lot of information. To provide a seamless experience, you should pick a strategy to split the response into smaller chunks that can be streamed to the client. Here are a few patterns to choose from (these names are informal):</p><ul><li>Snowballing raw response — each chunk re-sends the full accumulated response</li><li>Snowballing structured object — accumulated, valid JSON is emitted token-by-token</li><li>Full-schema delta streaming — stream deltas within a stable schema</li><li>Structured field streaming — one complete JSON field per chunk</li><li>Events streaming — distinguish payload kinds</li><li>Delta patching — each chunk is a typed diff/operation applied to prior state</li></ul><h4>Snowballing raw response</h4><p>The first, naive pattern is straightforward. The agentic system emits the response token-by-token. Every new chunk contains the previous content plus the newly added parts, so the message grows with each chunk. Here is an example (I’ll stick to the SSE protocol in all examples):</p><pre>data: {&quot;response&quot;:<br><br><br>data: {&quot;response&quot;: &quot;Thank you &quot;<br><br><br>data: {&quot;response&quot;: &quot;Thank you for your&quot;</pre><p>As you can see, the emitted information is not structured in any meaningful way. It is sent token-by-token and it’s up to the client to decide whether the received data forms a valid JSON. This doesn’t differ much from a non-streamed response, since the client often has to wait for all tokens to arrive before deserializing. The next approach addresses this.</p><h4>Snowballing structured object</h4><blockquote><em>Used in: </em>LangGraph<em> (with default streaming mode — </em><em>values)</em></blockquote><p>A variation of the previous approach is to have a template JSON with empty fields. Each time the LLM generates a token it is inserted into one of the JSON fields. The app sends the accumulated response, containing the newly created parts and the previous ones. Here is an example to visualize it:</p><pre>data: {&quot;response&quot;: &quot;&quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}<br><br><br>data: {&quot;response&quot;: &quot;Thank you &quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}<br><br><br>data: {&quot;response&quot;: &quot;Thank you for your&quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}</pre><p>This approach allows rendering on every incoming token, making the UI feel more responsive. Every chunk is a valid JSON so the UI can update as each chunk arrives. The drawback is that the same data is sent repeatedly while only a small portion changes, making it inefficient as the JSON responses grow.</p><h4>Full-schema delta streaming</h4><blockquote><em>Used in: OpenAI Chat Completions</em></blockquote><p>This problem can be tackled by sending only the tokens that were just generated. Again, each message is a JSON object, but previous responses do not accumulate — only the field that is being generated contains new tokens:</p><pre>data: {&quot;response&quot;: &quot;&quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}<br><br><br>data: {&quot;response&quot;: &quot;Thank you &quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}<br><br><br>data: {&quot;response&quot;: &quot;for your&quot;, &quot;suggestedFollowUps&quot;: [], &quot;recipes&quot;: []}</pre><p>This way each chunk is smaller and can arrive earlier than the accumulated ones in the previous approach.</p><p>The downside is that clients need to cache every chunk and combine them on their end. This approach works well for string fields but can be tricky for complex fields like arrays or objects.</p><h4>Structured field streaming</h4><p>The problem of non-string fields can be addressed by sending the entire field content in one chunk. Each chunk contains a complete field of the larger JSON:</p><pre>data: {&quot;response&quot;: &quot;Thank you for your meal planning request for healthy and fulfilling meals.&quot;}<br><br><br>data: {&quot;suggestedFollowUps&quot;: [&quot;Prepare a shopping list for the ingredients needed for the selected recipes.&quot;]}<br><br><br>data: {&quot;recipe&quot;: {&quot;id&quot;: 1234, &quot;name&quot;: &quot;Mexican Quinoa&quot;, ... }}</pre><p>A cost of this approach is that the time between chunks may increase, especially if an object or array contains a lot of data.</p><h4>Events streaming</h4><blockquote><em>Used in: Gemini Interactions API</em></blockquote><p>A variation of the previous approach is to send several fields that are logically connected instead of a single one. The response time for each chunk may increase compared to the previous approach, but in return we get a more consistent response from the agent. We can treat each chunk as an event/message sent to the client, with an id and type, which makes it easier to parse on the client side and to monitor and debug on the server side:</p><pre>event: rationale<br>data: {&quot;id&quot;: 1, &quot;type&quot;:&quot;rationale&quot;, &quot;response&quot;: &quot;Thank you for your meal planning request for healthy and fulfilling meals.&quot;}<br><br><br>event: suggestedFollowUps<br>data: {&quot;id&quot;: 2, &quot;type&quot;: &quot;suggestedFollowUps&quot;, &quot;suggestedFollowUps&quot;: [&quot;Prepare a shopping list for the ingredients needed for the selected recipes.&quot;]}<br><br><br>event: recipe<br>data: {&quot;id&quot;: 3, &quot;type&quot;: &quot;recipe&quot;, &quot;recipe&quot;: {&quot;id&quot;: &quot;6464b6f5-17bf-4744-90f6-dbaab3af9983&quot;,&quot;name&quot;: &quot;Mexican Quinoa&quot;, ... }}</pre><h4>Delta patching</h4><blockquote><em>Used in: ChatGPT’s and Perplexity’s web UI</em></blockquote><p>In many cases the previous approach is good enough, but for systems where we don’t want users to wait for each chunk, a variation of <em>Full-schema delta streaming</em> and <em>Events streaming</em> can be applied. This is useful for applications that send a lot of data while keeping the per-chunk size minimal.</p><p>As in <em>Events streaming</em>, each chunk is a separate event, but this time the event doesn’t gather related data into a single chunk. Instead, events represent which part of the JSON was generated by providing a field location and the value to add. This way string fields can be sent token-by-token as in <em>Full-schema delta streaming</em>, while complex fields can be sent in one go:</p><pre>data: {&quot;type&quot;:&quot;response.token&quot;,&quot;payload&quot;:&quot;Thank you&quot;}<br><br><br>data: {&quot;type&quot;:&quot;response.token&quot;,&quot;payload&quot;:&quot;for your&quot;}</pre><p>It can get more complicated for operations like adding an item to a list or replacing a previous chunk (e.g., if guard rails detect an incorrect chunk after it was sent). In such cases the app builds a final response using several approaches, one of which is sending events as JSON that consist of three fields describing the operation, location and value:</p><ul><li>o - operation: what action should be applied to the accumulated response, e.g. new, append, replace, add, etc.</li><li>p - path: references a location where the action should be applied (something like JSON Path), e.g. /message/parts/0 (which may translate to the first item of the parts array inside the message object)</li><li>v - value: the change (what should be added, replaced, removed, etc.)</li></ul><p>Examples:</p><pre>data: { &quot;o&quot;: &quot;append&quot;, &quot;p&quot;: &quot;/message/content&quot;, &quot;v&quot;: &quot;Thank you&quot;}<br><br><br>data: {&quot;o&quot;: &quot;add&quot;, &quot;p&quot;: &quot;/recipes/0&quot;, &quot;v&quot;: &quot;Crêpe&quot;}<br><br><br>data: {&quot;o&quot;: &quot;replace&quot;, &quot;p&quot;: &quot;/token_count&quot;, &quot;v&quot;: 1018}</pre><h3>Solution selection</h3><p>From the earlier sections we can tell there are many options to choose from, and they aren’t limited to the ones described here. So which one did I choose for my <strong><em>nutri-chef-ai</em></strong> project?</p><p>My main goal is to learn how to create and tune an AI agent, not to focus on the visual aspects of the project. Of course a nice UI would make working with the plan effortless and fun, but that’s additional work I don’t want to focus on right now. On the other hand, I don’t want to view only raw JSON — I want some visualizations.</p><p>After weighing the arguments, I decided to use <a href="https://chainlit.io/">Chainlit</a> for the UI. It’s an open-source app for building AI conversational solutions. It allows you to create apps similar to ChatGPT or Claude and supports customizations and custom building blocks (views, cards, elements), which mattered most to me.</p><p>To integrate with <em>Chainlit</em>, my meal planner had to expose an endpoint compatible with OpenAI’s streaming API. Therefore I chose HTTP and SSE as the transport protocol. Most chunks are sent as whole events; simple text is streamed token-by-token.</p><h3>Implementation</h3><h4>Agent Flow</h4><p>Here is how the logic of my agent looks:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CsRLrFPzzk8RdXcmpnomiw.png" /></figure><p>Before searching for any recipe, the app acknowledges receipt and starts processing the request. There are two phases — one without an LLM and one with it. The first phase (without the LLM) simply notifies the user that the request was accepted and is being processed. In <em>nutri-chef-ai</em> this appears as a static chunk:</p><pre>data:{&quot;type&quot;:&quot;status&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;phase&quot;:&quot;start&quot;,&quot;message&quot;:&quot;Starting meal proposal for: healthy fulfilling meals&quot;}}</pre><p>The next part involves the LLM in generating a more human-like answer that indicates how the request was understood. This is streamed token-by-token, so it looks like this:</p><pre>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;Thank&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; you&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; for&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; your&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; meal&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; planning&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; request&quot;}<br><br>// full answer: Thank you for your meal planning request for healthy fulfilling meals. I now search for suitable recipes.</pre><p>Once that is returned, the agent searches for matching recipes in the RAG database and informs the user about the steps along the way.</p><pre>data:{&quot;type&quot;:&quot;status&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;phase&quot;:&quot;search&quot;,&quot;message&quot;:&quot;Searching for matching recipes&quot;}}<br><br>data:{&quot;type&quot;:&quot;status&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;phase&quot;:&quot;search&quot;,&quot;message&quot;:&quot;Found 100 matching recipes&quot;}}</pre><p>Every step is sent in a single chunk, as is each recipe. The entire structure (shortened below) is sent in one go:</p><pre>data:{&quot;type&quot;:&quot;recipe.selected&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;recipeId&quot;:&quot;c01...&quot;,&quot;name&quot;:&quot;Power bowl with sweet potato&quot;,&quot;ingredients&quot;:[...],&quot;instructions&quot;:[...], &quot;imageUrl&quot;:&quot;https://...jpg&quot;, &quot;similarityScore&quot;:0.6263648178902887}}<br><br>data:{&quot;type&quot;:&quot;recipe.selected&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;recipeId&quot;:&quot;646..&quot;,&quot;name&quot;:&quot;Mexican Quinoa&quot;,&quot;ingredients&quot;:[...],&quot;instructions&quot;:[...],&quot;imageUrl&quot;:&quot;https://...jpg&quot;,&quot;similarityScore&quot;:0.6429639599585675}}</pre><p>Each recipe selection is finalized with a rationale explaining why it was chosen; again, the LLM generates this.</p><pre>data:{&quot;type&quot;:&quot;status&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:{&quot;phase&quot;:&quot;llm&quot;,&quot;message&quot;:&quot;Calling LLM (rationale)&quot;}}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;These&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; reci&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;pies&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot; ans&quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;wer &quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;the &quot;}<br><br>data:{&quot;type&quot;:&quot;response.token&quot;,&quot;ts&quot;:&quot;...&quot;,&quot;payload&quot;:&quot;query&quot;}<br><br>//These recipes answer the query &quot;healthy fulfilling meals&quot;, because they combine complete sources of protein, ...</pre><p>The last part of the response is follow-ups — suggestions for next actions or more detailed rationale:</p><pre>data:{&quot;type&quot;:&quot;suggested.follow.ups&quot;,&quot;ts&quot;:&quot;2026-08-06T05:27:39.451857100Z&quot;,&quot;payload&quot;:[&quot;Prepare a weekly meal plan and grocery list for a two-person household.&quot;,&quot;Check other recipes with similar ingredients to enrich my diet.&quot;]}</pre><h3>Code structure: callback approach</h3><p>How the code is organized: I keep core logic in domain services, without framework dependencies. If communication with an external system (service, database, etc.) is required, it happens via interfaces (ports) that can have multiple implementations. This approach is known as <em>Ports &amp; Adapters</em> or <em>Hexagonal architecture</em>.</p><p>The proposeMealStreaming(...) method of the MealPlanner class defines the flow for each response. At each step it uses specialized classes to execute a specific task: agents to produce output based on input (from user or another agent), and facades for more complex actions such as searching the vector database.</p><pre>data class RecipeProposals(<br>    val response: String,<br>    val suggestedFollowUps: List&lt;String&gt;,<br>    val recipes: List&lt;RecipeEntry&gt;,<br>)<br><br>data class RecipeEntry(<br>    val recipe: Recipe?,<br>)<br><br>@Service<br>class MealPlanner(<br>    private val recipeSearch: RecipeSearchFacade,<br>    private val acknowledgementAgent: AcknowledgementAgent,<br>    private val recipeSelectionAgent: RecipeSelectionAgent,<br>    private val rationaleAgent: RationaleAgent,<br>    private val suggestedFollowUpsAgent: SuggestedFollowUpsAgent,<br>) {<br>    companion object {<br>        private val log = logger {}<br>        private const val RECIPE_FETCH_LIMIT = 100<br>    }<br><br>    fun proposeMealStreaming(<br>        userPrompt: String,<br>        onEvent: (AiAgentEvent) -&gt; Unit,<br>    ) {<br>        log.info { &quot;Streaming meal proposals for prompt &#39;$userPrompt&#39;&quot; }<br>        onEvent(AiAgentEvent.PlanningStarted(userPrompt))<br><br>        ...<br>    }<br>}</pre><p>The crucial part of the fun proposeMealStreaming(...) function is the callback onEvent: (AiAgentEvent) -&gt; Unit. It is used to communicate with the outer (controller) layer of the application. Whenever a certain point in the workflow is reached (e.g., recipes have been selected by an agent), the onEvent(...) callback is executed to signal other parts of the application. These signals can then be mapped in the controller to DTOs and sent to the user.</p><pre>@RestController<br>@RequestMapping(&quot;/api/planner&quot;)<br>class MealPlannerController(<br>    private val mealPlanner: MealPlanner,<br>    private val mapper: AiAgentEventMapper,<br>) {<br><br>    @GetMapping(<br>        path = [&quot;/single&quot;],<br>        produces = [MediaType.TEXT_EVENT_STREAM_VALUE],<br>    )<br>    fun proposeMealSse(<br>        @RequestParam prompt: String,<br>    ): ResponseEntity&lt;SseEmitter&gt; {<br><br>        mealPlanner.proposeMealStreaming(prompt) { event -&gt;<br>            val sseEventDto = mapper.toSseEvent(event)<br>            // send sseEventDto<br>        }<br><br>        return ResponseEntity<br>            .ok()<br>            .contentType(MediaType.TEXT_EVENT_STREAM)<br>            .header(&quot;Cache-Control&quot;, &quot;no-cache&quot;)<br>            .header(&quot;X-Accel-Buffering&quot;, &quot;no&quot;)<br>            .body(sseEmitter)<br>    }<br>}</pre><p>This is an elegant approach for handling the asynchronous nature of the communication. This way we can send data to the client as it becomes available, without waiting for the entire workflow to finish.</p><blockquote><em>It’s also worth mentioning that in the above code I’m using Spring MVC as I’m more familiar with it, like many software engineers in the JVM ecosystem. There is however a more performant alternative — non-blocking, reactive approach with Spring WebFlux. For a highly efficient systems I would consider choosing WebFlux over Spring MVC.</em></blockquote><h4>Chunks</h4><p>Focusing on the heart of the <em>meal-planner</em> — the MealPlanner class:</p><pre>class MealPlanner(<br>    private val recipeSearch: RecipeSearchFacade,<br>    private val acknowledgementAgent: AcknowledgementAgent,<br>    private val recipeSelectionAgent: RecipeSelectionAgent,<br>    private val rationaleAgent: RationaleAgent,<br>    private val suggestedFollowUpsAgent: SuggestedFollowUpsAgent,<br>) {<br><br>    fun proposeMealStreaming(<br>        userPrompt: String,<br>        onEvent: (AiAgentEvent) -&gt; Unit,<br>    ) {<br>        <br>         log.info { &quot;Streaming meal proposals for prompt &#39;$userPrompt&#39;&quot; }<br><br>        // Step 0: stream a brief acknowledgement so the user knows the input was received<br>        onEvent(AiAgentEvent.PlanningStarted(userPrompt))<br>        acknowledgementAgent.execute(userPrompt) { token -&gt; onEvent(AiAgentEvent.ResponseToken(token)) }<br><br>        // Step 1: fetch candidate recipes<br>        onEvent(AiAgentEvent.SearchingRecipes())<br>        val recipes = recipeSearch.findRecipes(userPrompt, RECIPE_FETCH_LIMIT)<br>        log.info { &quot;Found ${recipes.size} recipes&quot; }<br>        onEvent(AiAgentEvent.RecipesFound(recipes.size))<br><br>        // Step 2: LLM selects the best recipes<br>        val selectedRecipes = recipeSelectionAgent.execute(userPrompt, recipes)<br>        if (selectedRecipes.isEmpty()) {<br>            onEvent(AiAgentEvent.PlanFailed(&quot;Failed to find matching recipes. Please try again.&quot;))<br>            return<br>        }<br>        selectedRecipes.forEach { entry -&gt;<br>            entry.recipe?.let { onEvent(AiAgentEvent.RecipeSelected(it.id, it)) }<br>        }<br><br>        // Step 3: LLM streams the rationale for the selected recipes<br>        val rationaleRecipes = selectedRecipes.mapNotNull { it.recipe }<br>        rationaleAgent.execute(userPrompt, rationaleRecipes) { token -&gt; onEvent(AiAgentEvent.ResponseToken(token)) }<br><br>        // Step 4: LLM suggests follow-up actions<br>        val suggestedFollowUps = suggestedFollowUpsAgent.execute(userPrompt, rationaleRecipes)<br>        onEvent(AiAgentEvent.SuggestedFollowUps(suggestedFollowUps))<br>        onEvent(<br>            AiAgentEvent.PlanReady(<br>                RecipeProposals(<br>                    recipes = selectedRecipes,<br>                ),<br>            ),<br>        )<br>    }</pre><p>As mentioned earlier, the agent response is divided into steps. Most steps reduce to:</p><ol><li>an acknowledgment that indicates the next step in the agent’s “thought” process,</li><li>executing a specialized agent or class method (e.g., recipe search),</li><li>sending the result of the step.</li></ol><p>Both acknowledgements and results are sent as domain events via the onEvent(...) function. Each event implements the AiAgentEvent interface:</p><pre>sealed interface AiAgentEvent {<br>    data class PlanningStarted(<br>        val prompt: String,<br>    ) : AiAgentEvent<br>    <br>    data class ResponseToken(<br>        val token: String,<br>    ) : AiAgentEvent<br><br>    class SearchingRecipes : AiAgentEvent<br><br>    data class RecipesFound(<br>        val count: Int,<br>    ) : AiAgentEvent<br><br>    data class RecipeSelected(<br>        val recipeId: UUID,<br>        val recipe: Recipe?,<br>    ) : AiAgentEvent<br><br>    data class SuggestedFollowUps(<br>        val suggestions: List&lt;String&gt;,<br>    ) : AiAgentEvent<br><br>    data class PlanReady(<br>        val proposals: RecipeProposals,<br>    ) : AiAgentEvent<br><br>    data class PlanFailed(<br>        val reason: String,<br>    ) : AiAgentEvent<br>}</pre><p>They represent different responses and are sent to the controller layer, which serializes them into the appropriate format.</p><h4>Server-Sent Events &amp; NDJSON controllers</h4><p>Let’s look at how events emitted with the onEvent(...) method are consumed and pushed to a client. Here is an implementation for the endpoint that produces SSE:</p><pre>import org.springframework.web.servlet.mvc.method.annotation.SseEmitter<br>import java.util.concurrent.Executor<br>import java.util.concurrent.Executors<br><br>@RestController<br>@RequestMapping(&quot;/api/planner&quot;)<br>class MealPlannerController(<br>    private val mealPlanner: MealPlanner,<br>    private val mapper: AiAgentEventMapper,<br>    @Qualifier(&quot;mealPlannerExecutor&quot;) private val executor: Executor,<br>) {<br><br>    @GetMapping(<br>        path = [&quot;/single&quot;],<br>        produces = [MediaType.TEXT_EVENT_STREAM_VALUE],<br>    )<br>    fun proposeMealSse(<br>        @RequestParam prompt: String,<br>    ): ResponseEntity&lt;SseEmitter&gt; {<br>        val sseEmitter = SseEmitter(0L)<br>        executor.execute {<br>            try {<br>                mealPlanner.proposeMealStreaming(prompt) { event -&gt;<br>                    sseEmitter.send(mapper.toSseEvent(event))<br>                }<br>            } catch (e: Exception) {<br>                log.warn(e) { &quot;SSE stream failed&quot; }<br>                try {<br>                    sseEmitter.send(mapper.toSseEvent(AiAgentEvent.PlanFailed(e.message ?: &quot;Unknown error&quot;)))<br>                } catch (sendException: Exception) {<br>                    log.error(sendException) { &quot;Failed to send SSE error event after stream failure&quot; }<br>                }<br>            } finally {<br>                sseEmitter.complete()<br>            }<br>        }<br>        return ResponseEntity<br>            .ok()<br>            .contentType(MediaType.TEXT_EVENT_STREAM)<br>            .header(&quot;Cache-Control&quot;, &quot;no-cache&quot;)<br>            .header(&quot;X-Accel-Buffering&quot;, &quot;no&quot;)<br>            .body(sseEmitter)<br>    }<br>}</pre><p>Every AiAgentEvent is consumed, mapped to an SSE event, and then sent using SseEmitter. SseEmitter is a Spring class (a subclass of ResponseBodyEmitter) that allows sending multiple objects. Together with Executor.execute(...), which wraps the controller logic, this enables streaming to run off the HTTP request thread. Here is the Executor bean definition:</p><pre>@Configuration<br>class ExecutorConfig {<br>    @Bean(destroyMethod = &quot;shutdown&quot;)<br>    fun mealPlannerExecutor(): ExecutorService =<br>        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())<br>}</pre><p>SSE events should include certain fields — type, ts, and payload. A mapper translates domain events into this envelope, represented as AgentResponseDto:</p><pre>@Component<br>class AiAgentEventMapper(<br>    private val clock: Clock,<br>) {<br>    data class StatusEvent(<br>        val ts: Instant,<br>        val phase: String,<br>        val message: String,<br>    )<br><br>    private data class AgentResponseDto(<br>        val type: String,<br>        val ts: Instant,<br>        val payload: Any,<br>    )<br><br>    fun toSseEvent(event: AiAgentEvent): SseEmitter.SseEventBuilder =<br>        SseEmitter.event().data(toAgentResponseDto(event).toString(Charsets.UTF_8).trimEnd())<br><br>    fun toAgentResponseDto(event: AiAgentEvent): ByteArray {<br>        val now = clock.instant()<br>        val envelope =<br>            when (event) {<br>                is AiAgentEvent.PlanningStarted -&gt;<br>                    AgentResponseDto(<br>                        &quot;status&quot;,<br>                        ts = now,<br>                        payload = StatusEvent(ts = now, phase = &quot;start&quot;, message = &quot;Starting meal proposal for: ${event.prompt}&quot;),<br>                    )<br>                is AiAgentEvent.ResponseToken -&gt; AgentResponseDto(&quot;response.token&quot;, ts = now, payload = event.token)<br>                is AiAgentEvent.SuggestedFollowUps -&gt; AgentResponseDto(&quot;suggested.follow.ups&quot;, ts = now, payload = event.suggestions)<br>            }<br>        return &quot;${envelope.toJson()}\n&quot;.toByteArray(Charsets.UTF_8)<br>    }<br>}</pre><p>To keep it short, I limited the number of events to a couple of examples to show how they are mapped.</p><p>Besides SSE, the app supports the NDJSON response format. Its implementation is similar to the previous endpoint. Because the application’s domain communicates via domain events, it’s easy to consume events the same way and change only how DTOs are sent:</p><pre>@GetMapping(<br>        path = [&quot;/single&quot;],<br>        produces = [&quot;application/x-ndjson&quot;],<br>    )<br>    fun proposeMealNdJson(<br>        @RequestParam prompt: String,<br>    ): ResponseEntity&lt;StreamingResponseBody&gt; {<br>        val body =<br>            StreamingResponseBody { out: OutputStream -&gt;<br>                try {<br>                    mealPlanner.proposeMealStreaming(prompt) { event -&gt;<br>                        out.write(mapper.toAgentResponseDto(event))<br>                        out.flush()<br>                    }<br>                } catch (e: Exception) {<br>                    log.warn(e) { &quot;NDJSON stream failed&quot; }<br>                    out.write(mapper.toAgentResponseDto(AiAgentEvent.PlanFailed(e.message ?: &quot;Unknown error&quot;)))<br>                    out.flush()<br>                }<br>            }<br>        return ResponseEntity<br>            .ok()<br>            .contentType(MediaType.parseMediaType(&quot;application/x-ndjson&quot;))<br>            .header(&quot;Cache-Control&quot;, &quot;no-cache&quot;)<br>            .header(&quot;X-Accel-Buffering&quot;, &quot;no&quot;)<br>            .body(body)<br>    }</pre><p>Unlike SSE, NDJSON is not supported out-of-the-box by Spring MVC, so there is no custom emitter. Fortunately, it can be implemented using the generic StreamingResponseBody interface.</p><h4>Raw response and Chainlit UI</h4><p>Here is the end result of how the agent responds. Every chunk is sent to the client as it is produced, so the client doesn’t have to wait for a single large JSON:</p><pre>curl -N -H &quot;Accept: text/event-stream&quot; &quot;http://localhost:8080/api/planner/single?prompt=chocolate+cake&quot;</pre><p>And the result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*MkjOEj3DHgWaaASE.gif" /></figure><p>To make it nicer, I integrated with <em>Chainlit</em>, so I don’t have to read raw chunks. Instead I get a polished UI that can even show recipe images.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/0*vvszMDV_ebB_WmGm.gif" /></figure><p>I picked <em>Chainlit</em> because it allows easy UI customization. If you’re interested in how I did it, check the project codebase (link in the <em>Summary</em> section).</p><h3>Summary</h3><p>I hope this article gave you a clear idea of how to build a responsive Spring MVC endpoint suitable for an agentic application.</p><p>If you want the full project code, check my GitHub:</p><p><a href="https://github.com/wkrzywiec/nutri-chef-ai">GitHub - wkrzywiec/nutri-chef-ai</a></p><h3>References</h3><ul><li><a href="https://developers.openai.com/api/reference/resources/responses/methods/create">Create a model response</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers">Writing WebSocket servers - Web APIs | MDN</a></li><li><a href="https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events">HTML</a></li><li><a href="https://medium.com/@moali314/server-sent-events-a-comprehensive-guide-e4b15d147576">Server-Sent Events: A Comprehensive Guide</a></li><li><a href="https://medium.com/@AlexanderObregon/how-to-implement-server-sent-events-sse-in-spring-boot-620024272ccb">How to Implement Server-Sent Events (SSE) in Spring Boot</a></li><li><a href="https://github.com/Chainlit/chainlit">GitHub - Chainlit/chainlit: Build Conversational AI in minutes ⚡️</a></li></ul><blockquote><em>This article was written by me. ✨ AI was used only to proofread and correct grammar, punctuation, and typos — no content was generated by AI.</em></blockquote><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/055_ai-streaming/"><em>https://wkrzywiec.is-a.dev</em></a><em> on September 6, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=433f328c1554" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building AI-Powered Software: Model Embeddings]]></title>
            <link>https://wkrzywiec.medium.com/building-ai-powered-software-model-embeddings-da8d040166c9?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/da8d040166c9</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[retrieval-augmented-gen]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Wed, 22 Oct 2025 00:00:34 GMT</pubDate>
            <atom:updated>2025-10-25T12:56:06.628Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Do you want to build your first AI application but don’t know how? Are you afraid of missing out on the AI hype train? Don’t worry! These were my exact thoughts recently, so I decided to start a new series on this topic. In this article, I want to focus on how to provide your own data to an LLM using RAG. After a short introduction to the topic, we’ll dive into a practical guide on how to convert data into vectors and then use it in a real Spring Boot application-so buckle up!</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*cLYd0lt1FBwrWpS0.png" /></figure><p>The AI revolution is here. The release of ChatGPT in November 2022 ignited a new revolution in software, where generative AI plays a central role. New tools and patterns have emerged, enabling us as software engineers to build new, exciting projects. It’s hard to keep up because almost every week something new comes out.</p><p>At least for me, it’s sometimes hard to keep up with all the new things. To fight off my fear of missing out and to extend my professional toolbox, I have created this series in which I will describe how to build an application powered by AI. But before we start, keep in mind I am not an AI engineer and I have only basic knowledge of machine learning. I am not an expert in this field, but I hope that with this series it will become a little bit clearer to me, and perhaps you, the reader, will also learn something too.</p><p>(And I hope that this series won’t share the fate of previous series that I abandoned after one or two entries 😜🤞)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*kp_2sKecrdpoict_.jpg" /></figure><h3>The Project — 👨‍🍳 Nutri Chef AI</h3><p>I like to learn based on real-life projects. That’s why I’ve decided to build a nutrition assistance application, which will help me plan all my meals for the entire week. I want to keep my meals healthy and balanced, with all needed macronutrients at a good level.</p><p>Because I already have a large number of favorite recipes, I don’t want to rely on something from the Internet. I want my AI nutrition assistant to plan my meals based on my prompt and my own cookbook.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*ptHu_g_o23rDjLwZ.png" /></figure><p>This requirement brings us to the first problem — <strong>how to provide my entire cookbook to the AI?</strong></p><p>Theoretically, we could add all the knowledge to each prompt, but for several reasons, it may not be a good idea:</p><ul><li>it would increase costs — each token sent to AI costs money (no matter if you pay with a subscription or with the electric power of your locally installed AI model),</li><li>it would introduce latency — large inputs usually require the AI to process for much longer,</li><li>it could result in losing the context window — it is not always good to have as big an input as possible because too large inputs may cause some parts to not be taken into account when generating an answer by the AI model; it is always a matter of balancing the size,</li><li>it would not be accepted by the AI model — each AI model has an input token limit which can’t be exceeded.</li></ul><p>Instead, we could use only a subset of the knowledge base. We can carefully select only the parts that are most relevant for a task (user prompt). For example, in the <em>Nutri Chef AI</em> app, for a prompt like find all recipes with what is left in my fridge - cheese, ham, egg, paprika, flour, milk, it is not necessary to attach recipes that require other ingredients. We want to receive only those that match the criteria. So we need something smart enough to get only those parts of data that may be relevant before adding them to the system context.</p><h4>How Retrieval‑Augmented Generation (RAG) works</h4><p>There are two patterns for how we could achieve that:</p><ol><li>via Retrieval-Augmented Generation (RAG)</li><li>via tool calling</li></ol><p>As you may guess, this article is focused on the first approach (I’ll do a separate blog post on the latter topic sometime in the future).</p><p>RAG is a technique in which an application retrieves the most relevant pieces of information from an external data source (it could be a database, file, or something else) and attaches it to the AI input along with a user prompt. It is up to the application to select data that may be needed to fulfill the task by the AI agent. The app builds the context specific to each user query.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*OpgLJLAAlPx8jDea.png" /></figure><p><strong>But how an application can select only relevant data?</strong></p><p>One way could be based on the keywords included in the user input. This is how Google Search, Elasticsearch, or any other “classic” solution works. They try to find the best matches based on key terms from the input. If the input is low-carb pasta dinner, it may find all documents that have low-carb, pasta, and dinner terms in them. They only analyze the words included in the query and, based on them, prepare the results. They usually do not search for deeper meaning, so in results for the mentioned query, we could get not only low-carb pasta recipes but also regular pasta recipes and low-carb but non-pasta recipes.</p><p>There is another approach that addresses this problem — embedding-based retrieval, also known as semantic retrieval.</p><h4>Embedding-based retrieval</h4><p>In short, an embedding is a compact representation of data in the form of a multidimensional vector. These vectors represent the (semantic, underlying) meaning of the embedded text. In embedding-based retrieval, we compare a user input, represented as a vector, with the entire knowledge base, also represented as vectors. If, by many measures, two vectors are similar to each other, we consider the data they represent to be relevant.</p><p>To visualize it, let’s check the following graph of the x and y axes, which presents three vectors:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*98KiaLF4ETaLQJX5.png" /></figure><p>Vectors A and B are similar to each other because they are close together, have a similar angle with the x and y axes, and are of similar length. Vector C, however, does not share those similarities with the other two vectors. If vectors A and B represented two pieces of information, we could say that both of them have a similar meaning.</p><p>When can we say that two vectors are similar? There are a couple of ways to calculate the distance metric between two vectors, and thus their similarity. Some of them you may already know from math classes:</p><ul><li>l2 (Euclidean) distance</li><li>cosine distance</li><li>l1 distance</li><li>hamming distance</li><li>jaccard distance</li></ul><p>Each distance metric has its strengths and is suitable for different data types. For instance, the Euclidean distance is the distance between two endpoints of a position vector, as shown in the picture below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*A_x9a3edbIMo36C8.png" /></figure><p>The larger the differences in length and angle between two vectors, the greater the distance is between them and the greater the dissimilarity.</p><p>Each method focuses on different aspects and should be chosen depending on the case. The best approach is to try out each of them and measure which one behaves the best.</p><h4>From text to vectors: producing embeddings</h4><p>So the app flow is pretty straightforward. When a user submits a query, we need to transform it into a vector and compare it with the stored vectors to find the most similar ones. But how do we make such a conversion of user input and data? With an AI model, of course!</p><p>Embedding models are specialized neural networks designed to convert text into vector representations, and are different from large language models used for chat. For instance, OpenAI is offering three at the moment of writing this — text-embedding-3-small, text-embedding-3-large and text-embedding-ada-002 . We can select whichever model we like, but we need to keep to these rules:</p><ul><li>Tune the number of dimensions based on purpose — a higher number of dimensions is usually good for capturing nuances in data; similar but still different data may have slightly different vectors and hence search may be more accurate. On the other hand, a higher number of parameters may require more resources to compute search and require more storage for larger vectors.</li><li>Model is self-hosted or consumed via API — like for LLMs, we pick from models that we can install locally or use services exposed by AI providers.</li><li>Input and data must be converted using the same model — each model represents space differently, which makes vectors produced not compatible if two different models were used. If you want to compare which embedding model works best for your use case (e.g., via A/B testing), you need to generate embeddings for your data using each model separately.</li></ul><p>If you would like to browse available models, go check <a href="https://huggingface.co/blog/mteb">https://huggingface.co/blog/mteb</a>, which also measures how powerful they all are.</p><h4>Chunking strategies</h4><p>One of the problems with data embedding is that the knowledge we would like to embed may be very large, and models may not handle too many input tokens. In fact, each one of them has its own limits.</p><p>Therefore, we need to chunk the data into smaller segments. The simple solution would be to split the data by character number. I.e., if a model has a limit of 1000 tokens, we could split the input to be less than that. But it would cause some embeddings to lack the required context, as they would be split in the middle of a sentence or section, which would produce a garbage vector.</p><p>Another approach is to chunk input data based on logical units, such as paragraphs, sections, or other meaningful divisions. For instance, the paragraphs of a blog post may be treated as separate chunks, or a section if a paragraph is too granular. This helps preserve context within each chunk, but we still need to ensure that each chunk does not exceed the model’s token limit, which can be checked using available libraries.</p><p>Next, a more sophisticated approach could be to logically split the data. If data is already structured, like recipes, chunking could be relatively easy since the data is already broken out into smaller pieces. But what if a knowledge base is not structured? We could use the LLM for that! It could split the data based on certain parameters (e.g., not losing context and not exceeding certain limits), which of course comes with additional cost and may not be accurate (as LLMs may produce nondeterministic results).</p><p>As we can see, there are various ways to chunk our data. As always, there is no silver bullet, and sometimes it is better to try out various approaches (different chunking strategies, chunk sizes, etc.) and measure their results.</p><h4>Storing vectors</h4><p>Once we’ve got vectors produced by the embedding model, we need to save them somewhere. There are a couple of options for doing that, like storing them in:</p><ol><li>a file — not efficient but a simple solution</li><li>an existing database which requires extensions installed. Popular solutions that offer it are, for instance, Postgres, Elasticsearch, Redis, and others.</li><li>a native vector DB — with the AI RAG revolution, new players have emerged offering native vector storing and search solutions. Examples are: <a href="https://www.trychroma.com/">Chroma</a>, <a href="https://milvus.io/">Milvus</a>, <a href="https://qdrant.tech/">Qdrant</a>, <a href="https://weaviate.io/">Weaviate</a>, and others.</li><li>a cloud provider IaaS — each cloud vendor has its own vector database, e.g. <a href="https://aws.amazon.com/s3/features/vectors/">Amazon S3 Vectors</a>, <a href="https://azure.microsoft.com/en-us/products/ai-services/ai-search">Azure AI Search</a> or Google’s <a href="https://cloud.google.com/vertex-ai/docs/vector-search/overview">Vertex AI Vector Search</a>.</li></ol><p>Each one of them comes with pros and cons. Some are free to use, some are paid but are easy to use, more performant, or do not require you to set up the entire infrastructure.</p><h4>Using embeddings in applications</h4><p>Phew, that was a lot of knowledge to be introduced to. Let’s now focus on the application that we’re building. In the <em>Nutri Chef AI</em> app, I want to have two endpoints that would:</p><ul><li>search the best fitting recipes</li><li>propose a meal plan for a day</li></ul><p>The workflow for the second one will look like this:</p><ol><li>User sends a query.</li><li>The query is transformed into a vector.</li><li>User input vector is compared with vectors stored in PostgreSQL database. The <em>n</em> most similar chunks are returned each with an id of a different recipe.</li><li>Full recipes are fetched from db using the recipe ids (this is where the 1st endpoint ends and returns recipes as results).</li><li>Recipes are formatted and added to the system prompt along with AI instructions and a user prompt.</li><li>The entire prompt is passed to the LLM.</li><li>The output is returned to the user as a string</li></ol><p>This flow is not specific to the <em>Nutri Chef AI</em>; it is a pretty generic one and could be used in various cases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*_3B4q5s4Gt_vii-l.png" /></figure><p>The implementation of this entire flow is described in the <a href="https://wkrzywiec.is-a.dev/posts/054_vector-db/#enriching-prompt-with-recipe-data">Enriching prompt with recipe data</a> section of this blog post.</p><h3>Practical walkthrough</h3><p>The theoretical introduction is now behind us, so we can move on to implementing a simple RAG in the <em>Nutri Chef AI</em> app. I’ve split this section into two parts:</p><ol><li>creating embeddings from already stored data</li><li>implementing RAG flow in the app itself</li></ol><h4>Embedding recipe data and inserting into pgvector columns</h4><p>The first step in building the Nutri Chef AI application is to vectorize the recipe data. I already have it in the PostgreSQL database, so the plan is to retrieve it, prepare the request for the embedding process, execute it, and then store the results in the PostgreSQL database.</p><p>My data is relatively static. I don’t change it too often, so I’ve decided that vectorizing the data will be a one-off job. The application will use the OpenAI model, so for embedding I’m also using the ChatGPT API.</p><p>To achieve this task, I could use <a href="https://platform.openai.com/docs/api-reference/embeddings/create">a simple endpoint for creating embeddings</a>, which is fast and returns vectors immediately. But in my case, I can wait a little longer for results, and if it’s cheaper, that’s even better. For these reasons, I’ve decided to go with the <a href="https://platform.openai.com/docs/guides/batch">OpenAI Batch API</a>. This API essentially processes requests in the same fashion as the standard one, but it allows you to combine multiple requests into one. They are then executed asynchronously, and the results are returned after a couple of minutes or hours (depending on how large the dataset is). It may add some overhead to the process (because we need to monitor the async process), but it comes with a lower price per embedding.</p><p>The overall process looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*g22cjviLi7kCQlDh.png" /></figure><p>And the steps are:</p><ol><li>Using Python script the data will be fetched from PostgreSQL database and prepared in the .jsonl format that is accepted by the OpenAI Batch API.</li><li>Prepared .jsonl file will be sent to OpenAI and then a process of embedding will be started.</li><li>Using the Python script the batch process will be monitored and once it’s done the resulting file will be downloaded and vectors will be inserted to the database.</li></ol><p>This is one of many approaches we could take. If you would like to learn what are the others, go check the <a href="https://wkrzywiec.is-a.dev/posts/054_vector-db/#different-ways-for-document-embedding-process">Different ways for document embedding process</a> section.</p><p>Before moving on to implementation of the scripts first we need to prepare the database. We need to install the pgvector extension by executing the SQL script:</p><pre>CREATE EXTENSION vector;</pre><p>Be aware that this extension is not built-in into the standard PostgreSQL version. You need to put the installation files into your PostgreSQL instance first. Or you could use the Docker image provided by creators of the extension:</p><pre>docker pull pgvector/pgvector:pg17-trixie</pre><p>More information on how to provide the installation files to the PostgreSQL instance could be found in <a href="http://github.com/pgvector/pgvector">the GitHub repository of the pgvector extension</a>.</p><h4>📦 Prepare batch input file</h4><p>Database is prepared so we could start writing a script for preparing .jsonl file for the batch job. This format is the only accepted by the OpenAI API and it looks somethig like this:</p><pre>{&quot;custom_id&quot;: &quot;61bf11bb_name&quot;, &quot;method&quot;: &quot;POST&quot;, &quot;url&quot;: &quot;/v1/embeddings&quot;, &quot;body&quot;: {&quot;model&quot;: &quot;text-embedding-3-small&quot;, &quot;input&quot;: &quot;Apple pie&quot;}}<br>{&quot;custom_id&quot;: &quot;61bf11bb_ingredients&quot;, &quot;method&quot;: &quot;POST&quot;, &quot;url&quot;: &quot;/v1/embeddings&quot;, &quot;body&quot;: {&quot;model&quot;: &quot;text-embedding-3-small&quot;, &quot;input&quot;: &quot;apples, flour, butter, sugar&quot;}}</pre><p>As you can see, it is just a file with regular JSONs where each line represents a separate request to the OpenAI API. We’ve got:</p><ul><li>custom_id - which is unique across a file, represents the id of a request,</li><li>method, url &amp; body- which are respectively HTTP method, path and body from &quot;regular&quot; API,</li></ul><p>So here is the Python script that generates it:</p><pre>import json<br>from pathlib import Path<br>from typing import Any, Dict, List<br>import psycopg2<br><br>DB_CONFIG = {<br>    &quot;host&quot;: &quot;localhost&quot;,<br>    &quot;port&quot;: 5432,<br>    &quot;database&quot;: &quot;meal_planner&quot;,<br>    &quot;user&quot;: &quot;postgres&quot;,<br>    &quot;password&quot;: &quot;postgres&quot;<br>}<br>EMBEDDING_MODEL = &quot;text-embedding-3-small&quot;<br><br>CHUNK_TYPES = [&quot;name&quot;, &quot;description&quot;, &quot;ingredients&quot;, &quot;instructions&quot;, &quot;tags&quot;]<br><br>def get_db_connection():<br>    return psycopg2.connect(**DB_CONFIG, client_encoding=&#39;UTF8&#39;)<br><br>def fetch_recipes(conn) -&gt; List[Dict[str, Any]]:<br>    print(&quot;Fetching recipes from database...&quot;)<br>    with conn.cursor() as cur:<br>        cur.execute(&quot;SELECT id, name, description, ingredients, instructions, tags FROM recipe&quot;)<br>        columns = [desc[0] for desc in cur.description]<br>        recipes = [dict(zip(columns, row)) for row in cur.fetchall()]<br>        return recipes<br><br>def chunk_recipe(recipe: Dict[str, Any]) -&gt; List[Dict[str, Any]]:<br>    print(f&quot;Chunking recipe {recipe[&#39;id&#39;]}...&quot;)<br>    chunks = []<br>    for chunk_type in CHUNK_TYPES:<br>        content = recipe.get(chunk_type)<br>        if content:<br>            chunks.append({<br>                &quot;recipe_id&quot;: recipe[&quot;id&quot;],<br>                &quot;chunk_type&quot;: chunk_type,<br>                &quot;content&quot;: content<br>            })<br>    print(f&quot;Created {len(chunks)} chunks for recipe {recipe[&#39;id&#39;]}&quot;)<br>    return chunks<br><br>def prepare_jsonl_file(chunks: List[Dict[str, Any]], path: str) -&gt; str:<br>    record_count = 0<br>    with open(path, &#39;w&#39;, encoding=&#39;utf-8&#39;) as f:<br>        for chunk in chunks:<br>            record = {<br>                &quot;custom_id&quot;: f&quot;{chunk[&#39;recipe_id&#39;]}_{chunk[&#39;chunk_type&#39;]}&quot;,<br>                &quot;method&quot;: &quot;POST&quot;,<br>                &quot;url&quot;: &quot;/v1/embeddings&quot;,<br>                &quot;body&quot;: {<br>                    &quot;model&quot;: EMBEDDING_MODEL,<br>                    &quot;input&quot;: str(chunk[&quot;content&quot;])<br>                }<br>            }<br>            f.write(json.dumps(record, ensure_ascii=False) + &#39;\n&#39;)<br>            record_count += 1<br>    <br>    print(f&quot;Created file with {record_count} records, path: {path}&quot;)<br>    return path<br><br>def main():<br>    print(&quot;Starting embedding process...&quot;)<br>    <br>    temp_dir = Path(&quot;batch-files&quot;)<br>    temp_dir.mkdir(exist_ok=True)<br>    print(f&quot;Created temporary directory at: {temp_dir}&quot;)<br>    <br>    try:<br>        conn = get_db_connection()<br>        recipes = fetch_recipes(conn)<br>        <br>        print(&quot;Starting recipe chunking process...&quot;)<br>        all_chunks = []<br>        for recipe in recipes:<br>            all_chunks.extend(chunk_recipe(recipe))<br>        print(f&quot;Created total of {len(all_chunks)} chunks from {len(recipes)} recipes&quot;)<br>        <br>        base_jsonl_path = temp_dir / &quot;recipes_for_embedding.jsonl&quot;<br>        jsonl_path = prepare_jsonl_file(all_chunks, str(base_jsonl_path))<br><br>    except Exception as e:<br>        print(f&quot;Error during embedding generation: {str(e)}&quot;)<br>        raise<br>    finally:<br>        print(&quot;Performing cleanup&quot;)<br>        if &#39;conn&#39; in locals():<br>            conn.close()<br>        print(&quot;Database connection closed&quot;)<br><br>if __name__ == &quot;__main__&quot;:<br>   main()</pre><p>And after running it we’ve got an output:</p><pre>Starting embedding process...<br>Created temporary directory at: batch-files<br>Fetching recipes from database...<br>Starting recipe chunking process...<br>Created total of 25 chunks from 5 recipes<br>Created file with 25 records, path: batch-files\recipes_for_embedding.jsonl<br>Performing cleanup<br>Database connection closed</pre><p>Simple as that. From the script, you can tell that I’m using the OpenAI API and I’ve picked the text-embedding-3-small embedding model. You can choose from different models, which vary by generation, performance, and price. At the moment of writing this article, there are also text-embedding-ada-002 and text-embedding-3-large. A current list of supported models can be found on <a href="https://platform.openai.com/docs/models">the official OpenAI Models website</a>.</p><p>Going back to the script, if you look closer you may see that each recipe’s data was split into smaller chunks (name, description, ingredients, instructions, and tags) to make each vector smaller.</p><h4>🚀Start batch job</h4><p>An input file is prepared, so we have nothing left but to write a script to first upload it and then start the batch job:</p><pre>import os<br>from pathlib import Path<br>import openai  <br>from datetime import datetime<br>from dotenv import load_dotenv<br><br><br>load_dotenv()<br><br>OPENAI_API_KEY = os.getenv(&quot;OPENAI_API_KEY&quot;)<br>file_path =  Path(&quot;batch-files&quot;)  / &quot;recipes_for_embedding.jsonl&quot;<br><br>def main():<br>    client = openai.OpenAI(api_key=OPENAI_API_KEY)<br>    <br>    file_response = client.files.create(<br>        file=open(file_path, &quot;rb&quot;),<br>        purpose=&quot;batch&quot;<br>    )<br>    file_id = file_response.id<br>    print(f&quot;File uploaded successfully with ID: {file_id}&quot;)<br>    <br>    descr = datetime.now().strftime(&quot;%Y%m%d_%H%M%S&quot;) + &quot; - nutri chef embedding&quot;<br>    batch_response = client.batches.create(<br>        input_file_id=file_id,<br>        endpoint=&quot;/v1/embeddings&quot;,<br>        completion_window=&quot;24h&quot;,<br>        metadata={<br>            &quot;description&quot;: descr<br>        }<br>    )<br>    job_id = batch_response.id<br>    print(f&quot;Batch job created with ID: {job_id}&quot;)<br><br>if __name__ == &quot;__main__&quot;:<br>   main()</pre><p>The output after running the script:</p><pre>HTTP Request: POST https://api.openai.com/v1/files &quot;HTTP/1.1 200 OK&quot;<br>File uploaded successfully with ID: file-HvRA1M3<br>HTTP Request: POST https://api.openai.com/v1/batches &quot;HTTP/1.1 200 OK&quot;<br>Batch job created with ID: batch_68c8f2bfTP/1.1 200 OK&quot; File uploaded successfully with ID: file-HvRA1M3 HTTP Request: POST https://api.openai.com/v1/batches &quot;HTTP/1.1 200 OK&quot; Batch job created with ID: batch_68c8f2b</pre><p>Great! 🎉🎉🎉 The only thing we can do now is sit and relax. The batch job is executed in the background, so how do we know when it finishes? 🤔</p><h4>🔍 Monitor batch job status</h4><p>Depending on how large your dataset is, the batch process may take from several minutes to several hours. To verify it, the OpenAI API can be used:</p><pre>from openai import OpenAI<br>from dotenv import load_dotenv<br>import os<br><br>load_dotenv()<br><br>OPENAI_API_KEY = os.getenv(&quot;OPENAI_API_KEY&quot;)<br>client = OpenAI(api_key=OPENAI_API_KEY)<br><br>batch_id = &quot;batch_68c8f2bf&quot;<br><br>batch = client.batches.retrieve(batch_id)<br>print(f&quot;Batch response:\n {batch.to_json()}&quot;)</pre><p>This simple Python script will print out the batch job information:</p><pre>{<br>  &quot;id&quot;: &quot;batch_68c8f2bf&quot;,<br>  &quot;completion_window&quot;: &quot;24h&quot;,<br>  &quot;created_at&quot;: 1757999807,<br>  &quot;endpoint&quot;: &quot;/v1/embeddings&quot;,<br>  &quot;input_file_id&quot;: &quot;file-HvRA1M3&quot;,<br>  &quot;object&quot;: &quot;batch&quot;,<br>  &quot;status&quot;: &quot;completed&quot;,<br>  &quot;cancelled_at&quot;: null,<br>  &quot;cancelling_at&quot;: null,<br>  &quot;completed_at&quot;: 1757999947,<br>  &quot;error_file_id&quot;: null,<br>  &quot;errors&quot;: null,<br>  &quot;expired_at&quot;: null,<br>  &quot;expires_at&quot;: 1758086207,<br>  &quot;failed_at&quot;: null,<br>  &quot;finalizing_at&quot;: 1757999941,<br>  &quot;in_progress_at&quot;: 1757999869,<br>  &quot;metadata&quot;: {<br>    &quot;description&quot;: &quot;20250916_071647 - nutri chef embedding&quot;<br>  },<br>  &quot;output_file_id&quot;: &quot;file-BhMQY4&quot;,<br>  &quot;request_counts&quot;: {<br>    &quot;completed&quot;: 25,<br>    &quot;failed&quot;: 0,<br>    &quot;total&quot;: 25<br>  },<br>  &quot;usage&quot;: {<br>    &quot;input_tokens&quot;: 3533,<br>    &quot;output_tokens&quot;: 0,<br>    &quot;total_tokens&quot;: 3533,<br>    &quot;input_tokens_details&quot;: {<br>      &quot;cached_tokens&quot;: 0<br>    },<br>    &quot;output_tokens_details&quot;: {<br>      &quot;reasoning_tokens&quot;: 0<br>    }<br>  }<br>}</pre><p>The most important information for us is the status. Above, it states that it is completed, and we can see that it also contains information about the output_file_id - a file with all results.</p><p>Before completing it a batch may have various statuses, all of them are listed <a href="https://platform.openai.com/docs/guides/batch#4-check-the-status-of-a-batch">here</a>. Most likely you will see one of these:</p><ul><li>in_progress - batch is running,</li><li>finalizing - all requests have been executed and the results are being prepared,</li><li>completed - all requests have been executed and the results are available to be downloaded.</li></ul><h4>💾 Download batch job results</h4><p>Having the id of an output file, we can download it with this simple script:</p><pre>from openai import OpenAI<br>from dotenv import load_dotenv<br>import os<br>from pathlib import Path<br>from datetime import datetime<br><br>load_dotenv()<br><br>OPENAI_API_KEY = os.getenv(&quot;OPENAI_API_KEY&quot;)<br>client = OpenAI(api_key=OPENAI_API_KEY)<br><br>file_name = &quot;file-BhMQY4&quot;<br><br>file_response = client.files.content(file_name)<br><br>output_dir = Path(&quot;batch-files&quot;)<br><br>output_file = output_dir / f&quot;{file_name}.jsonl&quot;<br><br>with open(output_file, &quot;w&quot;, encoding=&#39;utf-8&#39;) as f:<br>    f.write(file_response.text)<br><br>print(f&quot;Response saved to: {output_file}&quot;)</pre><p>The output:</p><pre>Response saved to: batch-files\file-BhMQY4.jsonl</pre><p>After opening the resulting file, we would get something like this:</p><pre>{<br>  &quot;id&quot;: &quot;batch_req_68c8f34a2aa&quot;,<br>  &quot;custom_id&quot;: &quot;61bf11bb_name&quot;,<br>  &quot;response&quot;: {<br>    &quot;status_code&quot;: 200,<br>    &quot;request_id&quot;: &quot;8724e1825b5baa0&quot;,<br>    &quot;body&quot;: {<br>      &quot;object&quot;: &quot;list&quot;,<br>      &quot;data&quot;: [<br>        {<br>          &quot;object&quot;: &quot;embedding&quot;,<br>          &quot;index&quot;: 0,<br>          &quot;embedding&quot;: [<br>            -0.0075121797,<br>            0.016524209,<br>            0.012069914,<br>            -0.032091618,<br>           .... other values<br>          ]<br>        }<br>      ],<br>      &quot;model&quot;: &quot;text-embedding-3-small&quot;,<br>      &quot;usage&quot;: {<br>        &quot;prompt_tokens&quot;: 13,<br>        &quot;total_tokens&quot;: 13<br>      }<br>    }<br>  },<br>  &quot;error&quot;: null<br>}</pre><p>It contains a list of responses for each request, where each one contains a vector representation of each chunk, which will be extracted and inserted into the database in the next step.</p><h4>📥 Insert vector data into database</h4><p>The last step is to insert the embeddings into the PostgreSQL database. We could use the already existing table with recipes, but because each recipe was chunked into five pieces, let’s have a new one:</p><pre>CREATE TABLE recipe_embeddings (<br>    id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),<br>    recipe_id               UUID NOT NULL REFERENCES recipe(id),<br>    chunk_type              VARCHAR(255),<br>    token_count             INTEGER,<br>    embedding               VECTOR(1536)<br>);</pre><p>where:</p><ul><li>id - is an id of a row,</li><li>recipe_id - is an id of a recipe,</li><li>chunk_type - tells what kind of chunk it is, values like name, description, ingredients, instructions, tags,</li><li>token_count - tells how many tokens this chunk translates to,</li><li>embedding - is the stored vector.</li></ul><p>Having this table set we can execute a following Python script:</p><pre>import json<br>import psycopg2<br>import tiktoken<br>from typing import List, Dict, Any<br>from pathlib import Path<br><br>embedding_file = &quot;file-BhMQY4.jsonl&quot;<br><br>DB_CONFIG = {<br>    &quot;host&quot;: &quot;localhost&quot;,<br>    &quot;port&quot;: 5432,<br>    &quot;database&quot;: &quot;meal_planner&quot;,<br>    &quot;user&quot;: &quot;postgres&quot;,<br>    &quot;password&quot;: &quot;postgres&quot;<br>}<br>EMBEDDING_MODEL = &quot;text-embedding-3-small&quot;<br><br>_loaded_request_data = None<br><br>def get_token_count(text: Any, model: str) -&gt; int:<br>    if text is None:<br>        text = &quot;&quot;<br>    if not isinstance(text, str):<br>        text = str(text)<br>    <br>    text = text.replace(&#39;\x00&#39;, &#39;&#39;)<br>    <br>    enc = tiktoken.encoding_for_model(model)<br>    try:<br>        token_count = len(enc.encode(text))<br>        print(f&quot;Token count: {token_count:,} for text of length {len(text):,}&quot;)<br>        return token_count<br>    except Exception as e:<br>        print(f&quot;Error counting tokens for text: {text[:100]}... Error: {str(e)}&quot;)<br>        return 0<br><br>def load_request_data():<br>    global _loaded_request_data<br>    if _loaded_request_data is None:<br>        _loaded_request_data = []<br>        with open(&#39;batch-files/recipes_for_embedding.jsonl&#39;, &#39;r&#39;, encoding=&#39;utf-8&#39;) as file:<br>            for line in file:<br>                _loaded_request_data.append(json.loads(line))<br><br>def find_content(custom_id):<br>    load_request_data()<br>    <br>    for item in _loaded_request_data:<br>        if item.get(&#39;custom_id&#39;) == custom_id:<br>            return item.get(&#39;body&#39;, {}).get(&#39;input&#39;)<br>    <br>    print(f&quot;Content not found for custom_id: {custom_id}&quot;)<br>    return None<br><br>def load_embeddings(file: str) -&gt; List[Dict[str, Any]]:<br>    all_embeddings = []<br>    <br>    file_path = Path(&quot;batch-files&quot;) / file<br>    if not file_path.exists():<br>        print(f&quot;Embedding file not found: {file_path}&quot;)<br>        return all_embeddings<br>    <br>    print(f&quot;Loading embeddings from file: {file_path}&quot;)<br>    with open(file_path, &quot;r&quot;, encoding=&#39;utf-8&#39;) as f:<br>        for line in f:<br>            try:<br>                record = json.loads(line.strip())<br>                custom_id = record.get(&quot;custom_id&quot;, {})<br>                recipe_id, chunk_type = custom_id.split(&#39;_&#39;, 1)<br><br>                embedding = record.get(&quot;response&quot;, []).get(&quot;body&quot;, []).get(&quot;data&quot;, [])[0].get(&quot;embedding&quot;, [])<br>                content = find_content(custom_id)<br>                token_count = get_token_count(content, EMBEDDING_MODEL)<br>                <br>                all_embeddings.append({<br>                    &quot;recipe_id&quot;: recipe_id,<br>                    &quot;chunk_type&quot;: chunk_type,<br>                    &quot;embedding&quot;: embedding,<br>                    &quot;token_count&quot;: token_count<br>                })<br>                    <br>            except json.JSONDecodeError as e:<br>                print(f&quot;JSON parsing error in {file_path}: {str(e)}&quot;)<br>            except Exception as e:<br>                print(f&quot;Error processing line in {file_path}: {str(e)}&quot;)<br>    <br>    print(f&quot;Loaded total of {len(all_embeddings)} embeddings from all files&quot;)<br>    return all_embeddings<br><br><br>def get_db_connection():<br>    conn = psycopg2.connect(**DB_CONFIG)<br>    print(&quot;Database connection established successfully&quot;)<br>    return conn<br><br><br>def store_embeddings(conn, embeddings: List[Dict[str, Any]]):<br>    print(&quot;Starting to store embeddings in database&quot;)<br>    with conn.cursor() as cur:<br>        for idx, emb in enumerate(embeddings, 1):<br>            cur.execute(&quot;&quot;&quot;<br>                INSERT INTO recipe_embeddings (recipe_id, chunk_type, embedding, token_count)<br>                VALUES (%s, %s, %s, %s)<br>            &quot;&quot;&quot;, (emb[&quot;recipe_id&quot;], emb[&quot;chunk_type&quot;], emb[&quot;embedding&quot;], emb[&quot;token_count&quot;]))<br>        conn.commit()<br>    print(f&quot;Successfully stored all {len(embeddings)} embeddings in database&quot;)<br><br>def main():<br>    try:<br><br>        all_embeddings = load_embeddings(embedding_file)<br>        conn = get_db_connection()<br><br>        if all_embeddings:<br>            print(f&quot;Storing total of {len(all_embeddings)} embeddings from all files&quot;)<br>            store_embeddings(conn, all_embeddings)<br>        else:<br>            print(&quot;No embeddings were generated&quot;)<br>    <br>    except Exception as e:<br>            print(f&quot;Error during embedding generation: {str(e)}&quot;)<br>            raise<br>    finally:<br>        print(&quot;Performing cleanup&quot;)<br>        if &#39;conn&#39; in locals():<br>            conn.close()<br>            print(&quot;Database connection closed&quot;)<br><br><br>if __name__ == &quot;__main__&quot;:<br>    main()</pre><p>Here’s what’s really happening under the hood (and don’t worry, it’s not as scary as it sounds!):</p><ol><li>First, the script grabs both the request file (that’s the one with all the original embedding requests) and the output file (the one OpenAI sends back with the results).</li><li>Then, for each line in the output file, it finds the matching custom_id in the request file to pull out the original input text. It also extracts the recipe_id, chunk_type, and the actual embedding vector, and counts the tokens in the input (because, well, tokens matter!).</li><li>Finally, it bundles all this info together and pops it into the database.</li></ol><p>That’s it! If you’re like me and sometimes get lost in the details, just follow the steps one by one. And if you spot a better way, let me know-I’m always up for learning something new.</p><p>Here is an example output from the script:</p><pre>Loading embeddings from file: batch-files\file-BhMQY4umjMy228GzgVqvgB.jsonl<br>Token count: 11 for text of length 28<br>Token count: 36 for text of length 89<br>Token count: 210 for text of length 509<br>Token count: 348 for text of length 855<br>....<br>Loaded total of 25 embeddings from all files<br>Database connection established successfully<br>Storing total of 25 embeddings from all files<br>Starting to store embeddings in database<br>Successfully stored all 25 embeddings in database<br>Performing cleanup<br>Database connection closed</pre><h4>Utilizing embeddings in application</h4><p>We have the embeddings prepared, so we can move on to the actual application. I’ll be writing it in Kotlin with the Spring framework because I feel the most comfortable with it, and since recently it also has its own AI module that I wanted to try out. If you prefer another language or framework, you can still read it since I’ve tried to keep the code as simple as possible so it’s easy to understand and translate to another language/framework.</p><h4>Search best fitting recipes</h4><p>The first endpoint that I would like to implement, and which will utilize RAG, is for searching the most matching queries to the user input. It will be the GET /api/recipes/search?limit=&lt;number of recipes&gt;&amp;prompt=&lt;user input&gt; which will return a list of recipes in well-structured JSON format. The limit query parameter refers to how many recipes a client wants to retrieve, and the prompt is a user input string, which could be anything, e.g., find low-carb options for breakfast.</p><p>Starting from the controller, which is rather standard:</p><pre>@RestController<br>@RequestMapping(&quot;/api/recipes&quot;)<br>class RecipeController(<br>    val recipeSearchFacade: RecipeSearchFacade<br>) {<br><br>    @GetMapping(&quot;/search&quot;)<br>    fun findRecipes(<br>        @RequestParam prompt: String?,<br>        @RequestParam(defaultValue = &quot;10&quot;) limit: Int<br>    ): ResponseEntity&lt;RecipeSearchResponse&gt; {<br><br>        lateinit var matches: List&lt;Recipe&gt;<br><br>        val duration = measureTimeMillis {<br>            matches = recipeSearchFacade<br>                .findRecipes(prompt, limit)<br>        }<br><br>        val response = RecipeSearchResponse(matches = matches, totalFound = matches.size, searchTimeMs = duration)<br><br>        return ResponseEntity.ok(response)<br>    }<br>}<br><br>data class RecipeSearchResponse(<br>    val matches: List&lt;Recipe&gt; = emptyList(),<br>    val totalFound: Int = 0,<br>    val searchTimeMs: Long = 0,<br>)<br><br>data class Recipe(<br>    val id: UUID,<br>    val name: String,<br>    val description: String? = null,<br>    val ingredients: List&lt;Ingredients&gt;,<br>    val instructions: List&lt;Instruction&gt;,<br>    val sourceUrl: String? = null,<br>    val servings: String? = null,<br>    val tags: List&lt;String&gt; = emptyList(),<br>    val similarityScore: Double? = null,<br>)<br><br>data class Ingredients(val section: String, val ingredients: String)<br><br>data class Instruction(val steps: List&lt;String&gt;, val section: String)</pre><p>The only thing that may raise an eyebrow is the measureTimeMillis block, which I&#39;ve added to measure the waiting time for a result from the facade, which includes all the steps required to retrieve recipes. And speaking of the steps that the facade implements, here is its code:</p><pre>@Service<br>class RecipeSearchFacade(<br>    private val embeddingEngine: EmbeddingEngine,<br>    private val repository: RecipeRepository<br>) {<br><br>    companion object {<br>        private val log = logger {}<br>    }<br><br>    fun findRecipes(prompt: String?, limit: Int): List&lt;Recipe&gt; {<br>        if (prompt == null || prompt.isBlank()) return emptyList()<br>        log.info { &quot;Searching for $limit recipes based on a user prompt: &#39;$prompt&#39;...&quot;}<br><br>        val promptEmbedding: FloatArray = embeddingEngine.embed(prompt)<br>        log.info { &quot;User input was embedded. Looking for closest recipes...&quot; }<br>        return repository.findNearestRecipes(promptEmbedding, limit)<br>    }<br>}</pre><p>The role of a facade is to orchestrate the entire process; therefore, it covers first creating the embedding out of user input and then finding the most suitable recipes.</p><p>Let’s check the code of the EmbeddingEngine:</p><pre>import org.springframework.ai.embedding.EmbeddingModel<br><br>interface EmbeddingEngine {<br>    fun embed(prompt: String): FloatArray<br>}<br><br>@Component<br>class OpenAIEmbeddingEngine(<br>    private val embeddingModel: EmbeddingModel<br>): EmbeddingEngine {<br>    override fun embed(prompt: String): FloatArray {<br>        val response = embeddingModel.embedForResponse(listOf(prompt))<br>        return response.result.output<br>    }<br>}</pre><p>Again, there are not that many lines of code here. With EmbeddingModel, we can call any embedding model to get the vector representation of a user prompt, which in Kotlin is a FloatArray object. It&#39;s thanks to the <a href="https://spring.io/projects/spring-ai">Spring AI</a> framework. The EmbeddingModel is Spring&#39;s interface for interacting with various embedding models. The only thing that needs to be done to add it is to insert the following dependency to the build tool (Gradle or Maven):</p><pre>dependencies {<br>    implementation(&quot;org.springframework.ai:spring-ai-starter-model-openai:1.0.1&quot;)<br>    implementation(&quot;org.springframework.ai:spring-ai-autoconfigure-model-openai:1.0.1&quot;)<br>}</pre><p>I’ve selected the OpenAI model to start, so this is the reason both libraries were added. Apart from that, we also need to select which embedding model we want to use (it needs to be the same as was used for embedding data in PostgreSQL) and provide the API key. The simplest way to achieve this is via autoconfiguration, which requires only providing those values to the application.yaml file:</p><pre>spring:<br>  ai:<br>    openai:<br>      api-key: ${OPENAI_API_KEY}<br>      embedding:<br>        options:<br>          model: &quot;text-embedding-3-small&quot;</pre><p>That’s pretty much all that needs to be done to enable text embedding.</p><p>With OpenAIEmbeddingEngine, the user input was transformed into a vector, so the only thing to do now is to pass this vector to the SQL query to find the best matching recipes. The logic of it is encapsulated in the findNearestRecipes(promptEmbedding: FloatArray, limit: Int = 10) method of the RecipeRepository class:</p><pre>@Repository<br>class RecipeRepository(<br>    private val jdbcTemplate: NamedParameterJdbcTemplate,<br>) {<br><br>    fun findNearestRecipes(promptEmbedding: FloatArray, limit: Int = 10): List&lt;Recipe&gt; {<br>        val sql = &quot;&quot;&quot;<br>            SELECT<br>              r.id AS recipe_id,<br>              r.name,<br>              r.description,<br>              re.similarity_score,<br>              r.ingredients,<br>              r.instructions,<br>              r.source_url,<br>              r.servings,<br>              r.tags<br>            FROM (<br>                SELECT<br>                  recipe_id,<br>                  MIN(embedding &lt;=&gt; CAST(:prompt_embedding AS vector)) AS similarity_score<br>                FROM recipe_embeddings<br>                GROUP BY recipe_id<br>                ORDER BY similarity_score ASC<br>                LIMIT :limit<br>            ) AS re<br>            JOIN recipe r ON r.id = re.recipe_id<br>            ORDER BY re.similarity_score ASC;<br>        &quot;&quot;&quot;<br><br>        val params = MapSqlParameterSource()<br>            .addValue(&quot;prompt_embedding&quot;, promptEmbedding)<br>            .addValue(&quot;limit&quot;, limit)<br><br>        return jdbcTemplate.query(sql, params, rowMapper())<br>    }<br><br>    fun rowMapper() = RowMapper&lt;Recipe&gt; { rs, _ -&gt;<br>        Recipe(<br>            id = UUID.fromString(rs.getString(&quot;recipe_id&quot;)),<br>            name = rs.getString(&quot;name&quot;),<br>            description = rs.getString(&quot;description&quot;),<br>            ingredients = parseFromJson&lt;Ingredients&gt;(rs.getString(&quot;ingredients&quot;)),<br>            instructions =  parseFromJson&lt;Instruction&gt;(rs.getString(&quot;instructions&quot;)),<br>            sourceUrl = rs.getString(&quot;source_url&quot;),<br>            servings = rs.getString(&quot;servings&quot;),<br>            tags =  parseFromJson&lt;String&gt;(rs.getString(&quot;tags&quot;)),<br>            similarityScore = rs.getDouble(&quot;similarity_score&quot;),<br>        )<br>    }<br><br>    private inline fun &lt;reified T&gt; parseFromJson(jsonString: String?): List&lt;T&gt; {<br>        if (jsonString.isNullOrBlank()) return emptyList()<br><br>        return try {<br>            jsonString.toObject&lt;List&lt;T&gt;&gt;()<br>        } catch (e: Exception) {<br>            emptyList()<br>        }<br>    }</pre><p>Here I’m using the NamedParameterJdbcTemplate to execute the raw SQL query, which finds the top n (10 by default) recipeIds from the recipe_embeddings table. The list of retrieved recipeIds is then used to get the full recipe data from the recipe table.</p><p>The most interesting part is how vectors are compared here:</p><pre>SELECT<br>    recipe_id,<br>    MIN(embedding &lt;=&gt; CAST(:prompt_embedding AS vector)) AS similarity_score<br>FROM recipe_embeddings<br>GROUP BY recipe_id<br>ORDER BY similarity_score ASC<br>LIMIT :limit</pre><p>I’m using the &lt;=&gt; operator provided by the <em>pgvector</em> extension. This particular operator finds the shortest value of cosine distance. The results are then sorted from the smallest to the largest value and limited to only the top n results.</p><p><em>pgvector</em> offers other methods of comparing vectors which are:</p><p>In order to find the best one for your case, you should try out all of them, experiment, and measure which one is the most suitable for you.</p><p>The jsonString.toObject&lt;List&lt;T&gt;&gt;() function code on the String object is an extension method (this is Kotlin&#39;s ability to add additional method to a class that is not part of the current codebase):</p><pre>class ObjectMapperConfig {<br><br>    companion object {<br>        val objectMapper = objectMapper()<br>    }<br>}<br><br>fun objectMapper() =<br>    jsonMapper {<br>        addModule(kotlinModule())<br>        findAndAddModules()<br>        serializationInclusion(JsonInclude.Include.NON_NULL)<br>        defaultDateFormat(StdDateFormat())<br>        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)<br>        build()<br>    }<br><br>fun Any.toJson(): String = objectMapper().writeValueAsString(this)<br><br>inline fun &lt;reified T&gt; String.toObject(): T = objectMapper().readValue(this)</pre><p>Everything is coded, so after starting the application and running the <strong>GET</strong> /api/recipes/search?prompt=&lt;my_prompt&gt; endpoint, where my prompt was a simple find low-carb options for breakfast, I got this result:</p><pre>{<br>    &quot;totalFound&quot;: 10,<br>    &quot;searchTimeMs&quot;: 873,<br>    &quot;matches&quot;: [<br>        {<br>            &quot;id&quot;: &quot;00280180-a2ca-4d1d-930e-7a8baf6ff187&quot;,<br>            &quot;name&quot;: &quot;Scrambled Eggs with Mushrooms on Toast&quot;,<br>            &quot;description&quot;: &quot;A tasty breakfast idea. Scrambled eggs and browned small mushrooms are placed on toasts and sprinkled with chopped parsley or chives.&quot;,<br>            &quot;ingredients&quot;: [<br>                {<br>                &quot;section&quot;: &quot;all&quot;,<br>                &quot;ingredients&quot;: [<br>                    &quot;4 eggs&quot;,<br>                    &quot;4 tablespoons butter&quot;,<br>                    &quot;1 tablespoon vegetable oil&quot;,<br>                    &quot;2 cups small mushrooms&quot;,<br>                    &quot;sea salt and freshly ground black pepper&quot;,<br>                    &quot;4 slices of toasted baguette&quot;,<br>                    &quot;2 tablespoons chopped parsley&quot;<br>                ]<br>                }<br>            ],<br>            &quot;instructions&quot;: [<br>                {<br>                &quot;section&quot;: &quot;all&quot;,<br>                &quot;steps&quot;: [<br>                    &quot;Prepare two small frying pans. Heat the first, add the oil and 2 tablespoons of butter and melt. Add washed and dried mushrooms and fry for a few minutes until nicely browned on each side. Season with salt and pepper.&quot;,<br>                    &quot;Meanwhile, toast the bread slices in a toaster, spread them with butter and place them on two plates.&quot;,<br>                    &quot;Heat the second pan and melt 2 tablespoons of butter in it, crack in the eggs and season with salt. Fry quickly, stirring only 4–5 times. As soon as the whites set, remove from the pan and place on one prepared toast. Put the mushrooms on the other toast. Sprinkle with pepper and parsley.&quot;<br>                ]<br>                }<br>            ],<br>            &quot;sourceUrl&quot;: &quot;https://www.kwestiasmaku.com/dania_dla_dwojga/sniadania/jajecznica_pieczarki/przepis.html&quot;,<br>            &quot;servings&quot;: &quot;2 servings&quot;,<br>            &quot;tags&quot;: [<br>                &quot;Mushrooms&quot;,<br>                &quot;Eggs&quot;,<br>                &quot;Eggs for breakfast&quot;<br>            ],<br>            &quot;similarityScore&quot;: 0.365850391911005<br>        },<br>        /// other recipes<br>    ]<br>}</pre><p>Looks amazing 🤩! As you can see, even with a small amount of work and without really using any LLM, we can “intelligently” query our data to find the best matches! Awesome!</p><h4>Enriching prompt with recipe data</h4><p>With previous success, let’s build a new thing on top of that — a simple diet advisor. Let’s use the LLM to act as a dietician that focuses on delivering nutritionally balanced recipes based on the user prompt (assuming that the user will be asking for ideas for a specific meal and giving certain boundaries, like a specific diet or favorite ingredients). It won’t generate a full meal plan for the entire week or even a full day-this will be covered in a future blog post.</p><p>The first thing to do is to define an endpoint and controller for that:</p><pre>@RestController<br>@RequestMapping(&quot;/api/planner&quot;)<br>class MealPlannerController(<br>    private val mealPlanner: MealPlanner<br>) {<br><br>    @GetMapping(&quot;/single&quot;)<br>    fun proposeMeal(<br>        @RequestParam prompt: String,<br>    ): ResponseEntity&lt;RecipeProposals&gt; {<br>        val proposals = mealPlanner.proposeMeal(prompt)<br>        return ResponseEntity.ok(proposals)<br>    }<br>}</pre><p>The MealPlanner is a service that takes responsibility for orchestrating the entire process, which is - finding the most relevant recipes to the user prompt (we already have that) and adding them to the request made to the AI model. Here is the entire code of this class:</p><pre>import org.springframework.ai.chat.client.ChatClient<br><br>data class RawRecipeProposals(val response: String, val nextActions: List&lt;String&gt;, val recipes: List&lt;RecipeIdWithRationale&gt;)<br><br>class RecipeIdWithRationale(val rationale: String, val recipeId: UUID)<br><br>data class RecipeProposals(val response: String, val nextActions: List&lt;String&gt;, val recipes: List&lt;RecipeWithRationale&gt;)<br><br>data class RecipeWithRationale(val rationale: String, val recipe: Recipe?)<br><br>@Service<br>class MealPlanner(<br>    private val recipeSearch: RecipeSearchFacade,<br>    private val builder: ChatClient.Builder<br>) {<br><br>    companion object {<br>        private val log = logger {}<br>    }<br><br>    fun proposeMeal(userPrompt: String): RecipeProposals? {<br>        log.info { &quot;Searching for best meal proposals based on user prompt... &#39;$userPrompt&#39;&quot;}<br>        val recipes = recipeSearch.findRecipes(userPrompt, 10)<br><br>        log.info {&quot; Found ${recipes.size} recipes with ids: ${recipes.map { it.id }} &quot;}<br>        log.info { &quot;Calling an AI agent...&quot;}<br>        val answer = builder.build().prompt()<br>            .system(<br>                &quot;&quot;&quot;<br>                You are a nutrition assistant that selects the best fitting recipes for meal planning.<br>                <br>                TASK: Analyze the provided recipes and select the most suitable ones based on nutritional benefits and user goals. If no specific goal is provided, assume recommendations for a regular healthy adult diet.<br>                <br>                RESPONSE FORMAT: You must respond with valid JSON in exactly this structure:<br>                {<br>                  &quot;response&quot;: &quot;Brief overall explanation of your selection strategy and nutritional focus&quot;,<br>                  &quot;nextActions&quot;: [&quot;suggested action 1&quot;, &quot;suggested action 2&quot;],<br>                  &quot;recipes&quot;: [<br>                    {<br>                      &quot;recipeId&quot;: &quot;recipe-uuid-here&quot;,<br>                      &quot;rationale&quot;: &quot;Detailed explanation of why this recipe was selected, focusing on specific nutritional benefits&quot;<br>                    }<br>                  ]<br>                }<br>                <br>                REQUIREMENTS:<br>                - Select 3-5 most suitable recipes from the provided list<br>                - Focus on nutritional balance, variety, and health benefits<br>                - Include specific nutritional reasons in each rationale<br>                - Suggest 2-3 relevant next actions for the user<br>                - Use the exact recipe IDs provided<br>                - Respond in the same language as the user&#39;s request<br>                - Return only valid JSON, no additional text<br>                <br>                RECIPES: ${recipes.toJson()}<br>                &quot;&quot;&quot;.trimIndent()<br>            )<br>            .user { u -&gt; u.text(&quot;USER_QUERY: \&quot;$userPrompt\&quot;&quot;) }<br>            .call()<br>            .content()<br>        log.info { &quot;Response from AI Agent:\n $answer&quot; }<br>        return answer?.let {<br>            mapToRawRecipeProposals(it)<br>        }?.let {<br>            mapToRecipeProposals(it, recipes)<br>        }<br>    }<br><br>    private fun mapToRawRecipeProposals(answer: String): RawRecipeProposals? {<br>        return answer.toObject&lt;RawRecipeProposals&gt;()<br>    }<br><br>    private fun mapToRecipeProposals(raw: RawRecipeProposals, fullRecipes: List&lt;Recipe&gt;): RecipeProposals? {<br>        return RecipeProposals(<br>            response = raw.response,<br>            nextActions = raw.nextActions,<br>            recipes = raw.recipes.map { RecipeWithRationale(rationale = it.rationale, recipe = fullRecipes.find { recipe -&gt; recipe.id == it.recipeId}) }<br>        )<br>    }<br>}</pre><p>In the first lines of the fun proposeMeal(userPrompt: String) method, we&#39;re invoking the already existing recipe search code. The result of it is then transformed into JSON and added to the end of the system prompt. For now, let&#39;s not focus too much on the prompt itself; the whole topic of how to write a good prompt will be covered in another article.</p><p>In this post, I want to focus on two key points — how the recipes are added to the system prompt and how to enforce the AI to respond in a certain format. In the system prompt, I’m adding those retrieved recipes as JSONs, but in your system, you can try to use different formats like XML or something less structured. Because they are added to the prompt, it is always important to keep in mind that the content of each recipe should be relatively small in order to be below the limit of how many tokens can be added in the request to the AI model.</p><p>The second important thing in this prompt is how the output result should be returned because further processing of the AI response depends on that. This is a crucial part because if we want to build a complex system with an AI in the middle of it, we need to enforce returning structured responses from it to make it easier to process by other elements of our system (backend or UI code).</p><p>To make it work, we also need to specify which AI model we want to use. It needs to be added to the configuration in the application.yaml file. I&#39;ve decided to use OpenAI&#39;s gpt-5-mini model:</p><pre>spring:<br>  ai:<br>    openai:<br>      api-key: ${OPENAI_API_KEY}<br>      chat:<br>        options:<br>          model: &quot;gpt-5-mini&quot;<br>          temperature: 1</pre><p>After rebuilding the entire project, we can test the <strong>GET</strong> /api/planner/single endpoint with a prompt: looking for dinner ideas, I&#39;m vegetarian and my favorite vegetable is tomato. And here is an example result:</p><pre>{<br>  &quot;response&quot;: &quot;I selected vegetarian dishes from the list that maximize the use of tomatoes while providing balanced nutrients: good sources of plant/animal protein (eggs, cheese, beans), healthy fats (olive oil), fiber, and vitamins and antioxidants (lycopene, vitamin C, vitamin A) from vegetables. I focus on a variety of textures and seasons (warm one-pot dish, pasta, baked cheese, cold soup) so you have options for different moods and energy needs.&quot;,<br>  &quot;nextActions&quot;: [<br>    &quot;Choose 1 of the following recipes for today&#39;s dinner and I&#39;ll provide a shopping list.&quot;,<br>    &quot;If you want more protein, I can suggest simple modifications (e.g., more beans, addition of nuts/seeds).&quot;,<br>    &quot;Do you want a vegan version of any recipe? I&#39;ll suggest substitutes for cheese and eggs.&quot;<br>  ],<br>  &quot;recipes&quot;: [<br>    {<br>      &quot;rationale&quot;: &quot;Halloumi cheese in tomatoes with beans — a great combination of plant protein (beans) and protein/calcium memory from halloumi. Beans increase fiber, iron, and plant protein content, making the dish more filling and stabilizing blood glucose levels. Tomatoes and spices add lycopene and micronutrients. Note: halloumi is quite salty — if you limit sodium, use less cheese or choose less salty mozzarella/packaged cheese.&quot;,<br>      &quot;recipe&quot;: {<br>        &quot;id&quot;: &quot;8fbb8ce5-8367-4660-a984-5f5756ad8780&quot;,<br>        &quot;name&quot;: &quot;Halloumi cheese in tomatoes with beans&quot;,<br>        &quot;description&quot;: &quot;Halloumi cheese can not only be fried or grilled but also baked or cooked in tomato sauce. Cooking keeps the cheese moist and pleasantly soft.&quot;,<br>        &quot;ingredients&quot;: [<br>          {<br>            &quot;section&quot;: &quot;all&quot;,<br>            &quot;ingredients&quot;: [<br>              &quot;600 g fresh tomatoes (e.g., sauce variety) or 400 g canned peeled&quot;,<br>              &quot;1 tablespoon olive oil&quot;,<br>              &quot;2 garlic cloves&quot;,<br>              &quot;1 can (400 g) white beans&quot;,<br>              &quot;about 200 g halloumi cheese&quot;,<br>              &quot;parsley and chives&quot;,<br>              &quot;spices: 1/2 teaspoon smoked paprika, a pinch of chili flakes, 1 teaspoon oregano, salt and pepper&quot;<br>            ]<br>          }<br>        ],<br>        &quot;instructions&quot;: [<br>          {<br>            &quot;section&quot;: &quot;all&quot;,<br>            &quot;steps&quot;: [<br>              &quot;Peel fresh tomatoes, cut into quarters, remove stems. Dice the flesh. Cut canned tomatoes into smaller pieces, keep the sauce.&quot;,<br>              &quot;In a large pan, gently fry grated garlic in olive oil. Add chopped fresh or canned tomatoes with the sauce and bring to a boil, season with salt, pepper, and other spices.&quot;,<br>              &quot;Cook until the tomatoes reduce (about 10 minutes for fresh tomatoes or 6 minutes for canned tomatoes).&quot;,<br>              &quot;Add canned beans with the liquid and mix. Cook for about 3 minutes.&quot;,<br>              &quot;Place halloumi cheese on top and cook for another 5 minutes. Sprinkle with chopped parsley and chives.&quot;<br>            ]<br>          }<br>        ],<br>        &quot;sourceUrl&quot;: &quot;https://www.kwestiasmaku.com/przepis/pieczony-ser-halloumi-w-pomidorach-z-fasola&quot;,<br>        &quot;servings&quot;: &quot;2 servings&quot;,<br>        &quot;tags&quot;: [<br>          &quot;Gluten-free&quot;,<br>          &quot;Warm dinners&quot;,<br>          &quot;halloumi&quot;,<br>          &quot;Greek cuisine&quot;,<br>          &quot;Fit recipes&quot;,<br>          &quot;Dinners&quot;,<br>          &quot;Lunches&quot;,<br>          &quot;Tomatoes&quot;,<br>          &quot;Vegetarian&quot;,<br>          &quot;Beans&quot;,<br>          &quot;Hits of Kwestia Smaku&quot;<br>        ],<br>        &quot;similarityScore&quot;: null<br>      }<br>    },<br>    //other proposals<br>  ]<br>}</pre><p>Looks amazing 🤩! As you can see even with small amount of work and with out really using any LLM we can “intelligently” query our data to find best matches! Awesome!</p><p>It returns not only the list of matched recipes but also the rationale for each recipe. There is also a text summary for the entire prompt with actions that the user may want to do next to get better results.</p><h3>Going deeper</h3><p>That’s pretty much it. With only a few lines of code, we have built a RAG system! It is very simple, but it may already make an impact. So what’s next? How could we make it even better? Oh, there are a lot of things that may be done better.</p><p>For instance, before providing the user prompt to the recipe semantic search service, we could first ask another AI agent to analyze the user input and produce a better prompt that would be embedded and then used to search for proper recipes. With this technique, we could get even more precise results, focusing on finding the most fitting but also nutritious meals possible from the cookbook.</p><p>This is just one of many possible improvements to make the RAG system more precise, faster, or cheaper. Before wrapping up, I want to briefly describe two additional enhancements you can consider.</p><h4>Indexing vectors</h4><p>The knowledge base for this project is relatively small. Finding the most similar vectors across thousands of vectors is a relatively quick operation. But what if we had billions or even trillions of vectors? Definitely, the retrieval process could get significantly longer.</p><p>One way of coping with that is to create indexes with <em>pgvector</em> that would cluster similar vectors into a smaller number of groups of vectors. With indexes, we do not search for vectors with the shortest distance but for groups of vectors. This way, we lose a little bit of the precision of finding the exact vector, but in return, we gain faster retrieval.</p><p>The <em>pgvector</em> extension supports two algorithms for creating such indexes — <strong>HNSW</strong> (Hierarchical Navigable Small World) &amp; <strong>IVFFlat</strong> (Inverted File Flat). I won’t be going deep into either of them, but you can check these great articles on <a href="https://www.crunchydata.com/blog/hnsw-indexes-with-postgres-and-pgvector">the first</a> and <a href="https://www.tigerdata.com/blog/nearest-neighbor-indexes-what-are-ivfflat-indexes-in-pgvector-and-how-do-they-work">the second</a> algorithm respectively.</p><h4>Different ways for document embedding process</h4><p>In the hands-on section of this article, I’ve described how I wrote the Python script that takes care of creating embeddings. I’ve decided to go with a Python script and use the batch API of OpenAI because, for my case, it is a one-time job. I don’t modify my cookbook too often, so the entire procedure could be done only once and never repeated. However, this is not the case in every system. Data in some systems changes quite often, and if new data should be embedded, then other approaches should be taken.</p><p>One of them could be to run a similar script periodically on a monthly, weekly, or daily basis. The script would either wipe down all existing vectors and replace them with new ones or only update and add those that have changed or were added. This, of course, requires further development of a script and good monitoring of it since it becomes a crucial part of the data pipeline for the entire system.</p><p>If periodic processing is not enough and we would like to have those vectors stored at the moment of storing the corresponding data, then another approach should be taken. Instead of the script, the logic of creating the embedding should be baked into the application logic itself. When the data is updated/inserted into the database, the application should also create a vector and store it. This approach certainly has advantages, like the data could be searched semantically right away, but the caveat is that since we’re introducing the external dependency (embedding model), it may slow down the modification operation or even prevent them from happening. There are, of course, workarounds for those problems, but with the cost of higher complexity of the entire system and slowing down the process of embedding. As always, each solution has its own tradeoffs.</p><p>The last solution could be to use the <a href="https://github.com/timescale/pgai">pgai</a> extension for PostgreSQL. It enables you to create and synchronize vector embeddings directly in the PostgreSQL instance. You don’t need to add any logic to the app or create complicated scripts to periodically update the entire database. With a simple configuration, all of that could be achieved inside PostgreSQL. This way, we move the logic from the app to the database, which in some cases could be a good solution, but in others it may be bad because it makes the entire data flow less explicit.</p><h3>Summary</h3><p>This is it for today! I hope you learned something new, like how to transform your data into vector embeddings, how to use them for semantic search, and how to write your first AI agent that leverages your own knowledge base.</p><p>The entire codebase for this project can be found on my GitHub: <a href="https://github.com/wkrzywiec/nutri-chef-ai">wkrzywiec/nutri-chef-ai</a>.</p><p>Until the next time!</p><h3>References</h3><p><a href="https://www.oreilly.com/library/view/ai-engineering/9781098166298/">AI Engineering</a></p><p><a href="https://www.datacamp.com/tutorial/introduction-to-text-embeddings-with-the-open-ai-api">https://www.datacamp.com/tutorial/introduction-to-text-embeddings-with-the-open-ai-api</a></p><ul><li><a href="https://www.tigerdata.com/blog/a-beginners-guide-to-vector-embeddings">A Beginner&#39;s Guide to Vector Embeddings</a></li><li><a href="https://severalnines.com/blog/vector-similarity-search-with-postgresqls-pgvector-a-deep-dive">Vector Similarity Search with PostgreSQL&#39;s pgvector</a></li><li><a href="https://www.tigerdata.com/blog/postgresql-as-a-vector-database-using-pgvector">PostgreSQL as a Vector Database: A Pgvector Tutorial</a></li><li><a href="https://weaviate.io/blog/chunking-strategies-for-rag">Chunking Strategies to Improve LLM RAG Pipeline Performance | Weaviate</a></li></ul><p><a href="https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipyn">https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipyn</a>b</p><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/054_vector-db/"><em>https://wkrzywiec.is-a.dev</em></a><em> on October 22, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da8d040166c9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Is Event Sourcing hard? Part 2: How to store events]]></title>
            <link>https://wkrzywiec.medium.com/is-event-sourcing-hard-part-2-how-to-store-events-bc6e34593897?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/bc6e34593897</guid>
            <category><![CDATA[event-sourcing]]></category>
            <category><![CDATA[events]]></category>
            <category><![CDATA[eventstore]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Mon, 15 Jul 2024 00:00:05 GMT</pubDate>
            <atom:updated>2024-07-24T04:27:26.920Z</atom:updated>
            <content:encoded><![CDATA[<p><em>You have decided that event sourcing is a great fit for your project and meets all your needs. The next step is to figure out how to persist events. While there are several tools available off the shelf, what if you could build your own event store technology? In this article, I will cover how to build the very first version of such a solution based on PostgreSQL, which can be utilized in Java applications.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*aa2U6_fgwQZe_iDT.jpg" /></figure><p>The key element in event sourcing are (surprise, surprise) events. These events are created after each business action is performed on a domain object (aggregate) and need to be persisted somewhere. They are also used to rebuild the state of domain objects, but first, they need to be fetched from some kind of storage. For these (and other) reasons, the central piece of an event sourcing system is the <strong>event store</strong> which will be built through out this post.</p><p>If you are not familiar with event sourcing, please check my previous article on that topic. — <a href="https://medium.com/@wkrzywiec/is-event-sourcing-hard-part-1-lets-build-a-domain-object-from-events-5b6bbacb2713">Is Event Sourcing hard? Part 1: Let’s build a domain object from events</a>.</p><p>An event store is crucial in event sourcing applications. But what requirements should it meet?</p><ul><li><strong>Event store should allow storing events.</strong></li><li><strong>Events stored in the event store can’t be modified</strong> — Events are business actions that occurred in the past. We cannot change history, and we cannot change events stored in the event store. Therefore, everything stored there remains as it was.</li><li><strong>Events must be added to the end of a stream</strong> — Every event persisted must be ordered, at least within a stream. A stream is a collection of events that refer to a single domain object. The event store should forbid saving any event in any position other than the end of a stream.</li><li><strong>All events in a stream are ordered</strong> — Not only must storing events preserve their order, but loading them must also maintain the order of their occurrence. This is crucial for replaying them to rebuild the state of a domain object.</li></ul><p>These are the requirements that will be covered by the implementation of the event store in this article. Fully-fledged event stores provide other capabilities, such as:</p><ul><li><strong>Events can be fetched up to a certain point in time</strong> — Sometimes, there is a need to see how the domain object looked in the past. Therefore, only a subset of events can be loaded, starting from the beginning and finishing at the event that occurred before the given point in time. That’s real time traveling in practice!</li><li><strong>Events are globally ordered</strong> — It’s beneficial to order events not only within a stream but also across streams, especially when interaction between domain objects is needed or building projections from multiple streams.</li><li><strong>Notify subscribers about changes in a stream</strong> — An event store may not only play the role of data storage but also act as a pub-sub solution. Whenever a new event is appended to a stream, subscribers (e.g., a service responsible for updating projections) are notified about it to perform their tasks.</li><li><strong>Store bi-temporal events</strong> — An event store may not only store information about the past but can also be used to store facts about the future! Bi-temporal event sourcing is a technique of adding a new property validAt to events, representing when they are valid and not when they were stored. This allows storing events from the future, which may be beneficial if we&#39;re sure that something will happen at a certain point in time. For example, a discount can be applied to a product in an e-commerce shop before Black Friday, but it will be valid only on that day.</li></ul><p>These are only examples of capabilities. There are certainly more, and you can check them in one of the off-the-shelf solutions, which are either fully operational databases or frameworks built on top of popular databases (e.g., PostgreSQL):</p><p>That’s it for the core concepts of an event store. There are, of course, more nuances, but for this blog post, I’ll limit it to the basics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*VVyQ_DWSOaLdm-xH.gif" /></figure><p>Let’s get our hands dirty and implement these concepts in a Java application using PostgreSQL as the storage technology.</p><blockquote><strong><em>Readers discretion is adivsed</em></strong><em> — the following implementation is only one of many possible ones. Depending on the project, requirements, and team preferences, it may look different. There is no one implementation to rule them all. I want you to keep that in mind; this is my preferred solution for my pet project. But of course, you’re more than welcome to share your opinion so we can all learn from each other.</em></blockquote><h3>Creating a Database Table</h3><p>The first step is to define a table where all events will be persisted. Here is the SQL statement to create a simple events table:</p><pre>CREATE TABLE IF NOT EXISTS events<br> (<br>    id                  UUID NOT NULL,<br>    stream_id           UUID NOT NULL,<br>    type                VARCHAR(256) NOT NULL,<br>    data                JSONB NOT NULL,<br>    added_at            TIMESTAMP WITH TIME ZONE DEFAULT NOW(),<br>);</pre><p>Where:</p><ul><li>id - is the identification of an event,</li><li>stream_id - is the ID of the domain object that the event pertains to,</li><li>type - is the definition of the event type (e.g., OrderDelivered),</li><li>data - all information that is part of an event is stored here. The JSONB column type indicates that it is stored in a JSON format,</li><li>added_at - is the timestamp of when the event was added/occurred.</li></ul><p>This statement can be part of a data migration tool, such as Liquibase or Flyway.</p><p>And that’s pretty much it (at least for now). We have a very simple table where we can store events. Therefore, let’s move on to implementing the part of the code that is responsible for saving events.</p><h3>Storing Events</h3><p>The first step is to define the interface of the event store so that we can have multiple implementations of it:</p><pre>public interface EventStore {<br><br>    void store(EventEntity event);<br><br>    default void store(List&lt;EventEntity&gt; events) {<br>        events.forEach(this::store);<br>    }<br>}</pre><p>Nothing fancy here. The class that will implement this interface will need to cover the store(EventEntity event) method. In the case of a list of events, the interface already provides a default implementation.</p><p>The EventEntity object represents the record from the database table that was just defined:</p><pre>@Data<br>public class EventEntity {<br><br>    protected UUID id;<br>    protected UUID streamId;<br>    protected String type;<br>    protected DomainEvent data;<br>    protected Instant addedAt;<br>}</pre><p>The DomainEvent in my case is an interface. Each event holds its own set of data but must provide information about the ID of the stream into which the event will be stored:</p><pre>public interface DomainEvent {<br>    UUID streamId();<br>}</pre><p>And here is an example of a concrete class, which is a Java record:</p><pre>record DeliveryCanceled(<br>        UUID orderId,<br>        String reason<br>) implements DomainEvent {<br><br>    @Overrides<br>    default UUID streamId() {<br>        return orderId();<br>    }<br>}</pre><p>Since in the context of a delivery the streamId is the same as the orderId, the method defined in the interface returns the orderId.</p><p>Returning to the EventStore interface, here is an implementation of the store(EventEntity event) method:</p><pre>import com.fasterxml.jackson.core.JsonProcessingException;<br>import com.fasterxml.jackson.databind.ObjectMapper;<br>import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;<br>import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;<br>import org.springframework.jdbc.core.namedparam.SqlParameterSource;<br><br>import java.sql.Timestamp;<br>import java.util.List;<br><br>@Slf4j<br>public class PostgresEventStore implements EventStore {<br><br>    private final NamedParameterJdbcTemplate jdbcTemplate;<br>    private final ObjectMapper objectMapper;<br><br>    public PostgresEventStore(NamedParameterJdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {<br>        this.jdbcTemplate = jdbcTemplate;<br>        this.objectMapper = objectMapper;<br>    }<br><br>    @Override<br>    public void store(EventEntity event) {<br>        log.info(&quot;Saving event in a store. Event: {}&quot;, event);<br><br>        var bodyAsJsonString = mapEventData(event.data());<br><br>        SqlParameterSource parameters = new MapSqlParameterSource()<br>                .addValue(&quot;event_id&quot;, event.id())<br>                .addValue(&quot;stream_id&quot;, event.streamId())<br>                .addValue(&quot;type&quot;, event.type())<br>                .addValue(&quot;data&quot;, bodyAsJsonString)<br>                .addValue(&quot;added_at&quot;, Timestamp.from(event.addedAt()));<br><br>        jdbcTemplate.update(&quot;&quot;&quot;<br>                INSERT INTO events(id, stream_id, type, data, added_at)<br>                SELECT :event_id, :stream_id, :type, :data::jsonb, :added_at<br>                &quot;&quot;&quot;,<br>                parameters)<br>        );<br>        log.info(&quot;Event was stored.&quot;);<br>    }<br><br>    private String mapEventData(DomainEvent domainEvent) throws RuntimeException {<br>        try {<br>            return objectMapper.writeValueAsString(domainEvent);<br>        } catch (JsonProcessingException e) {<br>            throw new RuntimeException(&quot;Failed to map DomainEvent to JSON before storing event.&quot;, e);<br>        }<br>    }<br>}</pre><p>Since my application is written with Spring Boot, the NamedParameterJdbcTemplate was used to integrate with PostgreSQL. Apart from that, the only other role of the method is to transform the Java object of the DomainEvent into its JSON String representation, for which the Jackson ObjectMapper is used.</p><p>If you would like to test if this code is really working, you can check the integration test that I’ve written, which is available here: <a href="https://github.com/wkrzywiec/farm-to-table/blob/master/services/commons/src/test/groovy/io/wkrzywiec/fooddelivery/commons/infra/store/PostgresEventStoreIT.groovy">PostgresEventStoreIT.groovy</a> .</p><h3>Loading Events</h3><p>Persisting events is covered, so let’s now implement the code for fetching them. First, we need to add a method definition to the interface:</p><pre>public interface EventStore {<br><br>    List&lt;EventEntity&gt; loadEvents(UUID streamId);<br>}</pre><p>And concrete implementation of it:</p><pre>@Slf4j<br>public class PostgresEventStore implements EventStore {<br><br>    private final NamedParameterJdbcTemplate jdbcTemplate;<br>    private final EventPostgresRowMapper eventPostgresRowMapper;<br>    private final ObjectMapper objectMapper;<br><br>    public PostgresEventStore(NamedParameterJdbcTemplate jdbcTemplate, ObjectMapper objectMapper, EventClassTypeProvider eventClassTypeProvider) {<br>        this.jdbcTemplate = jdbcTemplate;<br>        this.objectMapper = objectMapper;<br>        this.eventPostgresRowMapper = new EventPostgresRowMapper(objectMapper, eventClassTypeProvider);<br>    }<br><br>    @Override<br>    public List&lt;EventEntity&gt; loadEvents(UUID streamId) {<br><br>        return jdbcTemplate.query(&quot;&quot;&quot;<br>                SELECT id, stream_id, type, data, added_at<br>                FROM events<br>                ORDER BY added_at ASC;<br>                &quot;&quot;&quot;,<br>                eventPostgresRowMapper,<br>                streamId);<br>    }<br>}</pre><p>Nothing fancy here, except for the EventPostgresRowMapper and EventClassTypeProvider. These components are responsible for deserializing information stored in the database into EventEntity and its data part ( DomainEvent), respectively.</p><pre>@RequiredArgsConstructor<br>@Slf4j<br>class EventPostgresRowMapper implements RowMapper&lt;EventEntity&gt; {<br><br>    private final ObjectMapper objectMapper;<br>    private final EventClassTypeProvider caster;<br><br>    @Override<br>    public EventEntity mapRow(ResultSet rs, int rowNum) throws SQLException {<br>        String eventType = rs.getString(&quot;type&quot;);<br>        DomainEvent event = extractData(rs, eventType);<br>        return new EventEntity(<br>                rs.getString(&quot;id&quot;),<br>                rs.getString(&quot;stream_id&quot;),<br>                eventType,<br>                event,<br>                extractInstant(rs, &quot;added_at&quot;)<br>        );<br>    }<br><br>    private static Instant extractInstant(ResultSet rs, String columnName) throws SQLException {<br>        if (rs.getTimestamp(columnName) == null) {<br>            return null;<br>        }<br>        return rs.getTimestamp(columnName).toInstant();<br>    }<br><br>    private DomainEvent extractData(ResultSet rs, String eventType) throws SQLException {<br>        String bodyAsString = rs.getString(&quot;data&quot;);<br>        JsonNode bodyAsJsonNode = mapToJsonNode(bodyAsString);<br>        return mapEventBody(bodyAsJsonNode, eventType);<br>    }<br><br>    private JsonNode mapToJsonNode(String eventAsString) {<br>        try {<br>            return objectMapper.readTree(eventAsString);<br>        } catch (JsonProcessingException e) {<br>            log.error(&quot;Failed to parse event to json: {}&quot;, eventAsString);<br>            throw new RuntimeException(&quot;Parsing error&quot;, e);<br>        }<br>    }<br><br>    private DomainEvent mapEventBody(JsonNode eventBody, String eventType) {<br>        Class&lt;? extends DomainEvent&gt; classType = caster.getClassType(eventType);<br>        return mapEventBody(eventBody, classType);<br>    }<br><br>    private &lt;T extends DomainEvent&gt; T mapEventBody(JsonNode eventBody, Class&lt;T&gt; valueType) {<br>        try {<br>            return objectMapper.treeToValue(eventBody, valueType);<br>        } catch (JsonProcessingException e) {<br>            log.error(&quot;Failed to parse event json: {} to &#39;{}&#39; class&quot;, eventBody, valueType.getCanonicalName());<br>            throw new RuntimeException(&quot;Parsing error&quot;, e);<br>        }<br>    }<br>}</pre><p>The implementation of the EventPostgresRowMapper is quite generic and can be reused. It extends Spring&#39;s RowMapper, where we define how the ResultSet from the database should be used to build an EventEntity.</p><p>The only part that must be adjusted to specific DomainEvents is the EventClassTypeProvider, which is an interface that helps the ObjectMapper cast the result from the data column into the appropriate Java class:</p><pre>public interface EventClassTypeProvider {<br><br>    Class&lt;? extends DomainEvent&gt; getClassType(String type);<br>}</pre><p>And an example of its implementation:</p><pre>@Slf4j<br>class DeliveryEventClassTypeProvider implements EventClassTypeProvider {<br><br>    @Override<br>    public Class&lt;? extends DomainEvent&gt; getClassType(String type) {<br>        return switch (type) {<br>            case &quot;DeliveryCanceled&quot; -&gt; DeliveryEvent.DeliveryCanceled.class;<br>            default -&gt; {<br>                log.error(&quot;There is not logic for mapping {} event from a store&quot;, type);<br>                throw new IllegalArgumentException();<br>            }<br>        };<br>    }<br>}</pre><p>As before, tests for this functionality are available in <a href="https://github.com/wkrzywiec/farm-to-table/blob/master/services/commons/src/test/groovy/io/wkrzywiec/fooddelivery/commons/infra/store/PostgresEventStoreIT.groovy">PostgresEventStoreIT.groovy</a> class.</p><h3>Optimistic Locking for Strong Consistency</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/0*eHW-7e03cDnnemvS.gif" /></figure><p>The implementation we have so far is pretty basic. It may work for a small side project used by only one person, but it may not fit the needs of a larger solution (and I’m not claiming it’s production-ready yet!) where multiple users may try to store different events to the same stream at the same moment.</p><p>The foundation of the event store is that all events are stored in the order of their occurrence. But what happens if, at the same time, two threads want to append to the same stream? Which one should be accepted? Maybe both? If it were a bank transaction, do we want to accept two events that would reduce the account balance below zero? How do we ensure that events that should not happen are not stored in the event store?</p><p>One way of handling this would be pessimistic locking. It’s a database mechanism that prevents a row (or an entire table) from being read or modified if another transaction is making changes to it. If there is any chance that a thread reading data performs it at the moment when an update is occurring, the database will force the reading thread to wait until the update thread finishes.</p><p>This could be a good candidate for handling conflicts in the event store. However, besides the inherent problem of possible deadlock occurrence, it introduces additional limitations. Locking the entire table would introduce a bottleneck to the entire application because, in the case of adding a new event, reading them (or adding new ones to another stream) won’t be possible due to the lock. It would make the event store really slow, especially in the case of lots of write operations.</p><p>A better approach would be to use optimistic locking. This is not a built-in database mechanism, however. It’s a strategy for handling concurrent operations. In the “regular” approach to storing business information, optimistic locking is realized by introducing an additional integer column — version. An application first needs to load data from the database, including the version, and then it performs tasks based on it. As a result, it updates the same row in the database. Within an update statement, the current version of the row must be provided. If it is the same as in the database, the row is updated, and the version column is increased by one. If one of the threads tries to update a row but with an old version, the database should forbid it.</p><p>With the event store, we don’t follow this pattern since we don’t modify rows, but we can use optimistic locking to ensure that only correct events are stored and they are ordered.</p><p>The first step is to add a <em>BIGINT</em> version column to the events table:</p><pre>CREATE TABLE IF NOT EXISTS events<br> (<br>    stream_id           UUID NOT NULL,<br>    version             BIGINT NOT NULL,<br>    ... other columns<br><br>    UNIQUE (stream_id, version)<br>);</pre><p>Please note that besides adding a new column, I’ve added a unique constraint on the pair of stream_id and version columns. This way, we will ensure that there are no rows with the same pair of values.</p><p>However, this is not the only change that needs to be made. The concept of versions must be introduced to the code itself. The decision on how deeply this concept will penetrate the code is an important one to make.</p><p>The least invasive method would be to extend the storing method of the EventStore interface with the latestVersion parameter. This version still needs to be obtained from somewhere. REST endpoints must include it, and in a message-based system, incoming messages must include it. But the good news is that the version does not leak into the domain logic.</p><p>I, however, have decided to go in the opposite direction. I have introduced versions into the domain logic as well, mainly because I wanted to encapsulate the versioning logic within the domain layer to keep the application layer as thin as possible. Was it a good decision? Maybe. Maybe not. I’ll see in the future as the project evolves.</p><p>From <a href="https://medium.com/@wkrzywiec/is-event-sourcing-hard-part-1-lets-build-a-domain-object-from-events-5b6bbacb2713">the previous article</a>, you may remember that I generate DomainEvent within each domain method, e.g.:</p><pre>public class Delivery {<br><br>    private int version;<br>    private DeliveryStatus status;<br>    List&lt;DomainEvent&gt; changes = new ArrayList&lt;&gt;();<br><br>    public void cancel(String reason) {<br>        if (status != DeliveryStatus.CREATED) {<br>            throw new DeliveryException(format(&quot;Failed to cancel a %s delivery. It&#39;s not possible do it for a delivery with &#39;%s&#39; status&quot;, orderId, status));<br>        }<br>        this.status = DeliveryStatus.CANCELED;<br><br>        increaseVersion();<br>        changes.add(new DeliveryEvent.DeliveryCanceled(orderId, version, reason));<br>    }<br><br>    private void increaseVersion() {<br>        this.version = this.version + 1;<br>    }<br>}</pre><p>The introduction of versioning requires incrementing the version whenever a domain object is modified (hence the increaseVersion() method). It also necessitates introducing a version to each DomainEvent:</p><pre>public interface DomainEvent {<br>    UUID streamId();<br>    int version();<br>}</pre><p>And that’s pretty much what is peculiar in my project. Other parts should be the same or quite similar. So let’s jump into the new implementation of the EventStore.</p><p>But before that, let’s list all the requirements that it needs to cover because optimistic locking introduces these:</p><ul><li>Only events with a version succeeding the latest version within a stream can be stored.</li><li>Events with an already stored version can’t be persisted.</li><li>There should not be gaps between versions.</li><li>Loaded events are ordered by version.</li></ul><p>With all of this in mind, let’s see the implementation:</p><pre>public class PostgresEventStore implements EventStore {<br><br>    @Override<br>    public void store(EventEntity event) {<br>        log.info(&quot;Saving event in a store. Event: {}&quot;, event);<br><br>        var bodyAsJsonString = mapEventData(event.data());<br><br>        SqlParameterSource parameters = new MapSqlParameterSource()<br>                .addValue(&quot;event_id&quot;, event.id())<br>                .addValue(&quot;stream_id&quot;, event.streamId())<br>                .addValue(&quot;version&quot;, event.version())<br>                .addValue(&quot;type&quot;, event.type())<br>                .addValue(&quot;data&quot;, bodyAsJsonString)<br>                .addValue(&quot;added_at&quot;, Timestamp.from(event.addedAt()));<br><br>        int insertedRowsCount = 0;<br>        try {<br>            insertedRowsCount = jdbcTemplate.update(&quot;&quot;&quot;<br>                WITH latest_version AS (<br>                    SELECT MAX(version) AS value<br>                    FROM events<br>                    WHERE stream_id = :stream_id<br>                )<br>                INSERT INTO events(id, stream_id, version, type, data, added_at)<br>                SELECT :event_id, :stream_id, :version, :type, :data::jsonb, :added_at<br>                FROM latest_version<br>                WHERE :version = value + 1 OR :version = 0<br>                &quot;&quot;&quot;,<br>                    parameters<br>            );<br>        } catch (DuplicateKeyException exception) {<br>            log.error(&quot;Failed to persist event in event store due to duplicate key violation&quot;, exception);<br>            throw new InvalidEventVersionException();<br>        }<br><br>        if (insertedRowsCount == 0) {<br>            log.error(&quot;Event was not stored due to invalid event version&quot;);<br>            throw new InvalidEventVersionException();<br>        }<br>        log.info(&quot;Event was stored&quot;);<br>    }<br>}</pre><p>Phew, that’s a lot of code! Let’s unwrap it.</p><p>First of all, we need to know which is the latest version in a stream. Hence, the Common Table Expressions (CTE), WITH latest_version, was added to fetch it. Then it is used in the WHERE clause to verify that the version provided in the event is exactly one larger than the latest version. There is also another condition that checks if an event is the first one in a stream; for such events, the previous condition is not needed.</p><p>The jdbcTemplate.update(...) method is wrapped with a try ... catch ... block because the unique key constraint for stream_id and version was introduced. If a violation occurs, a custom InvalidEventVersionException is thrown.</p><p>The same exception is thrown if nothing is persisted in the database. This may happen if any of the mentioned conditions are not met.</p><p>The above implementation covers all requirements, and if you would like to verify it, there are tests available in the <a href="https://github.com/wkrzywiec/farm-to-table/blob/master/services/commons/src/test/groovy/io/wkrzywiec/fooddelivery/commons/infra/store/PostgresEventStoreIT.groovy">PostgresEventStoreIT.groovy</a> class.</p><blockquote><em>Please note that these are not the only changes that are needed. The ones that I have skipped include adding the </em><em>version field to the </em><em>EventEntity, adding deserialization logic for this field in </em><em>EventPostgresRowMapper, and adjusting the loading of events in the </em><em>PostgresEventStore class to order events by </em><em>version. These changes are very simple, so I have kept them out of this article, but they can be reviewed in the repository </em><a href="https://github.com/wkrzywiec/farm-to-table"><em>wkrzywiec/farm-to-table</em></a><em>.</em></blockquote><h3>Microservice world — one or per-service event store?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/371/0*LHmkU-_MJtH8SX3E.gif" /></figure><p>I really like the shape of the event store that we have so far, so let’s add another twist to it: how to fit an event store in a microservice world?</p><p>The rule of thumb in the microservice world is that shared databases must be avoided. Services should have their own databases and communicate with each other using an established API. Sooner or later, a shared database will lead to a situation where one of the services needs to introduce a non-backward-compatible change (e.g., rename/delete a column). Since other services are using the same database, they also need to change something on their side. This makes independent service deployment impossible. Deployment of all services must be done simultaneously with data migration.</p><p>For this main reason, most microservices have their own databases. So in the case of an event store, should we follow the same pattern? As always, it depends.</p><p>Let’s play devil’s advocate and try to figure out if a shared event store is a good idea. First of all, the problem mentioned above is rather non-existent because <strong>an event store schema is relatively stable</strong>. Moreover, event stores are append-only logs, so once we save data there, it should not be modified, which also limits the need for data migration. Besides addressing the major pain point of a shared database, a <strong>common event store may be very helpful in integrating services</strong>. By introducing a subscription mechanism, services may start to listen for certain events that are stored and act upon them. So there would be no need to have a fancy event bus (like Kafka or RabbitMQ). Also, having data in a single place makes it much easier to query, which may be very helpful in troubleshooting or preparing various reports.</p><p>Of course, having a single database has its downsides too. <strong>It is a single point of failure</strong> — if it does not work, most parts of your system are not working either. Also, with a large number of microservices, <strong>it must be prepared to handle a large number of concurrent connections</strong>, which might be quite a challenge even for a medium-sized distributed system. And finally, I mentioned that the database schema is stable and does not change, but that may not be true. It can evolve over time, especially when new concepts must be introduced (like global ordering or bi-temporal events) but weren’t planned ahead.</p><p>There might be, of course, other factors than the ones mentioned above. So whenever you need to decide which solution to use, don’t limit yourself to one way or another. Try to weigh all the pros and cons and decide which approach is better for you.</p><p>In my pet project, I have decided to use the shared event store, chiefly because it is a small project. A single event store removes the unnecessary task of managing multiple databases and finally gives me an opportunity to try to implement the subscription mechanism.</p><p>As a consequence of this decision a new column to the events table was added. The channel <em>VARCHAR</em> column stores information which service is writing to the particualr stream. In my case if it&#39;s an <em>ordering</em> service a value is ordering and if it&#39;s a <em>delivery</em> service a value if delivery. Pretty straight-forward.</p><p>As a consequence of this decision, a new column was added to the events table. The channel <em>VARCHAR</em> column stores information about which service is writing to the particular stream. In my case, if it&#39;s an <em>ordering</em> service, the value is &quot;ordering&quot;, and if it&#39;s a <em>delivery</em> service, the value is &quot;delivery&quot;. Pretty straightforward.</p><p>The implementation of this is simple (it is only the addition of a new column), so I’ll skip it here. But if you would like to check the final solution, go check the code in the <a href="https://github.com/wkrzywiec/farm-to-table">wkrzywiec/farm-to-table</a> repository.</p><h3>And Why Not Kafka?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*9V7PJA1chaiauwJw.gif" /></figure><p>Before we wrap up, let’s dive into one more very polarizing issue: <em>Is Kafka a good candidate for an event store?</em></p><p>At the first glance it is and there are lots of people that confirm that. But let’s check the requirements that were mentioned earlier, which an event store needs to fulfill, to figure it out on our own:</p><ul><li>✅ <strong>Event store should allow storing events</strong> — By default, Kafka stores messages for 2 weeks, but it is possible to extend the retention time infinitely.</li><li>✅ <strong>Events stored in the event store can’t be modified</strong> — Every message stored in Kafka can’t be modified.</li><li>✅ <strong>All events in a stream are ordered</strong> — Kafka has topic partitions and ensures that all messages within a given partition (in our case, it could be a streamId) are ordered.</li><li>✅ / ❌ <strong>Events must be added to the end of a stream</strong> — In a highly concurrent system where multiple producers write to the same topic partition, it is not possible to control the order of messages within it or to disallow incorrect ones. There is a proposal <a href="https://issues.apache.org/jira/browse/KAFKA-2260">KAFKA-2260</a> to be able to provide an offset during the write, but as of today, it is still open.</li><li>✅ / ❌ <strong>Events can be fetched up to a certain point in time</strong> — There is no built-in functionality, but there is a way to achieve it.</li><li>❌ <strong>Events are globally ordered</strong> — Kafka guarantees the order of messages only within a single partition. Events stored within a single or multiple topics would not be ordered.</li><li>✅ <strong>Notify subscribers about changes in a stream</strong> — Kafka allows subscribing to its topics and notifies consumers about adding new messages.</li><li>✅ <strong>Store bi-temporal events</strong></li></ul><p>Most of the points are green, so should we go ahead with Kafka? Not so fast.</p><p>Depending on the situation, a lack of one of these capabilities may be a showstopper. The biggest, in my eyes, is the fact that Kafka does not support and does not have a way to introduce optimistic locking. This means that in the case of concurrent writes, there will be no mechanism to protect one of these writes. For instance, let’s say that we have a delivery that is canceled and updated in 2 separate threads. With Kafka, we could end up with a situation where the update is written to the stream after the delivery is canceled (which, from a business point of view, should never happen).</p><p>Also, the lack of global ordering may be a blocker for those systems that need to merge events from multiple streams.</p><p>Therefore, in my opinion, for small systems, it may be a good candidate to consider, but there are cases where it’s better to keep it away. But still, Kafka is a great tool designed to move data from one place to another, and for that (not storage), it is the best solution.</p><p>I hope that after reading this article, you know more about event stores and how they can be implemented in a Java application using a PostgreSQL database. I encourage you to try it yourself, perhaps with a slightly different approach, to get the full experience of learning all its bits and pieces.</p><p>If you would like to check the entire code of my project, visit the GitHub repository: <a href="https://github.com/wkrzywiec/farm-to-table">wkrzywiec/farm-to-table</a>.</p><p>And if you would like to learn more about event stores and event sourcing, check the links below.</p><h3>References</h3><ul><li><a href="https://cqrs.wordpress.com/documents/building-event-storage/">Building an Event Storage</a></li><li><a href="https://event-driven.io/pl/lets_build_event_store_in_one_hour/">Let&#39;s build event store in one hour! - Event-Driven.io</a></li><li><a href="https://softwaremill.com/implementing-event-sourcing-using-a-relational-database/">Implementing event sourcing using a relational database | SoftwareMill</a></li><li><a href="https://blog.arkency.com/fixing-the-past-and-dealing-with-the-future-using-bi-temporal-eventsourcing/">Fixing the past and dealing with the future using bi-temporal EventSourcing</a></li><li><a href="https://event-driven.io/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing! - Event-Driven.io</a></li></ul><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/053_event-store-part-2/"><em>https://wkrzywiec.is-a.dev</em></a><em> on July 15, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc6e34593897" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Is Event Sourcing hard? Part 1: Let’s build a domain object from events]]></title>
            <link>https://wkrzywiec.medium.com/is-event-sourcing-hard-part-1-lets-build-a-domain-object-from-events-5b6bbacb2713?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/5b6bbacb2713</guid>
            <category><![CDATA[eventstore]]></category>
            <category><![CDATA[events]]></category>
            <category><![CDATA[event-sourcing]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Mon, 10 Jun 2024 00:00:30 GMT</pubDate>
            <atom:updated>2024-06-17T05:02:32.631Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Learn the concepts of event sourcing and how to implement a basic, non-production-ready application using it.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*c5-YgqfXyrSc2zkh.jpg" /></figure><p>What if I told you that there is a different way of storing business entities other than as entries in relational database tables, JSONs in document key-value stores, or graphs in graph databases? Of course, people can invent anything, but the key question is: will it be usable?</p><p>And the answer is yes-there is another way that is not just a different approach to the same problem but also adds value to traditional methods of storing entities. This method is called <strong>event sourcing</strong> and it is gaining momentum nowadays.</p><p>This is my first article in the “Is <em>event sourcing</em> hard?” series, where I’ll take a practical approach and try to implement key concepts of <em>event sourcing</em> in Java. In each article, I will tackle different aspects of it. For example, in this one, I’m focusing on explaining what <em>event sourcing</em> is and showing how it could look in code. In the next ones, I’ll focus on adding additional elements connected with <em>event sourcing</em>, like an <em>event store</em> or an outbox. Moreover, I’ll explore more advanced concepts such as how to version events, create snapshot events, and handle bi-temporal events. But that’s not everything I have planned for this series!</p><p>My article is, of course, not the first on this topic. In fact, if you google it, you will find a plethora of great resources far more advanced and in-depth than this one. For instance, a great blog by <a href="https://event-driven.io/">Oskar Dudycz — event-driven.io</a> is an excellent source of information on <em>event sourcing</em>.</p><p>With my series, I’m not trying to cover this topic exhaustively. It’s just my take on it, how I understand it, and a bit of playing around with its concepts.</p><h3>What is event souring?</h3><p>In essence, the concept is very simple. Instead of storing information about our models as database entries, we store facts about them. Rather than storing properties of an entity, we store all events that pertain to it.</p><p>To visualize this, let’s consider an example. Suppose we want to model a delivery service that provides food to homes. Here are some business events that can occur:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*6IJzHFywkfobQJCl.png" /></figure><p>Now, the idea is to store these events in an <strong>event store</strong>. When a new action needs to be performed on a specific delivery (e.g., it needs to be canceled), the application first needs to load all events and then process them to rebuild the current state of the delivery:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*03N07Gnez8NGdV_J.png" /></figure><p>There are multiple ways in which this could be achieved in the code. Here’s an example of the Delivery class, which includes a static factory method from(List&lt;Event&gt; events). This method accepts a list of events and produces the domain object:</p><pre>@Getter  <br>@EqualsAndHashCode  <br>@ToString  <br>public class Delivery {  <br>    private String orderId;  <br>    private String customerId;  <br>    private String farmId;  <br>    private String deliveryManId;  <br>    private DeliveryStatus status;  <br>    private String address;  <br>    private List&lt;Item&gt; items;  <br>    private BigDecimal deliveryCharge = new BigDecimal(0);  <br>    private BigDecimal tip = new BigDecimal(0);  <br>    private BigDecimal total = new BigDecimal(0);  <br>    private Map&lt;String, String&gt; metadata = new HashMap&lt;&gt;();<br><br>    private Delivery() {};<br><br>    public static Delivery from(List&lt;Event&gt; events) {<br>        Delivery delivery = null;<br>        <br>        for (Event event: events) {<br>            switch (event.body()) {<br>                case DeliveryCreated created -&gt; {<br>                    delivery = new Delivery();<br><br>                    delivery.orderId = created.orderId();<br>                    delivery.customerId = created.customerId();<br>                    delivery.farmId = created.farmId();<br>                    delivery.status = DeliveryStatus.CREATED;<br>                    delivery.address = created.address();<br>                    delivery.items = mapItems(created.items());<br>                    delivery.deliveryCharge = created.deliveryCharge();<br>                    delivery.total = created.total();<br><br>                    Map&lt;String, String&gt; metadata = new HashMap&lt;&gt;();<br>                    metadata.put(&quot;creationTimestamp&quot;, event.header().createdAt().toString());<br>                    delivery.metadata = metadata;<br>                }<br><br>                case DeliveryManAssigned deliveryManAssigned -&gt; {<br>                    delivery.deliveryManId = deliveryManAssigned.deliveryManId();<br>                }<br><br>                case FoodWasPickedUp foodWasPickedUp -&gt; {<br>                    var metadata = delivery.getMetadata();<br>                    metadata.put(&quot;foodPickedUpTimestamp&quot;, event.header().createdAt().toString());<br><br>                    delivery.metadata = metadata;<br>                    delivery.status = DeliveryStatus.FOOD_PICKED;<br>                }<br><br>                case FoodDelivered foodDelivered -&gt; {<br>                    var metadata = delivery.getMetadata();<br>                    metadata.put(&quot;foodDeliveredTimestamp&quot;, event.header().createdAt().toString());<br><br>                    delivery.metadata = metadata;<br>                    delivery.status = DeliveryStatus.FOOD_DELIVERED;<br>                }<br><br>                case TipAddedToDelivery tipAddedToDelivery -&gt; {<br>                    delivery.tip = tipAddedToDelivery.tip();<br>                    delivery.total = tipAddedToDelivery.total();<br>                }<br><br>                default -&gt; throw new IllegalStateException(&quot;Failed to replay events to build delivery object. Unhandled events: &quot; + event.body().getClass());<br>            }<br>        }<br>        return delivery;<br>    }<br><br>    private static List&lt;Item&gt; mapItems(List&lt;io.wkrzywiec.fooddelivery.delivery.domain.incoming.Item&gt; items) {<br>        return items.stream().map(dto -&gt; Item.builder()<br>                .name(dto.name())<br>                .amount(dto.amount())<br>                .pricePerItem(dto.pricePerItem())<br>                .build()).toList();<br>    }<br>}</pre><p>The idea is quite simple. The factory method iterates through the entire list of events (it’s important that events are sorted by their occurrence) and creates a new object from the information stored in an event and/or the previous state of the delivery.</p><p>To understand this better, let’s take a closer look. The first step of the method is to define a variable for the resulting Delivery, which initially is set to null. Then, the iteration through events begins, starting with the DeliveryCreated event that holds some data:</p><pre>public record DeliveryCreated (<br>  String orderId, String customerId,<br>  String farmId, String address, List&lt;Item&gt; items,<br>  BigDecimal deliveryCharge, BigDecimal total ) { }</pre><p>Based on this event, a private constructor is used to create an empty Delivery object and then set all the initial fields:</p><pre>public static Delivery from(List&lt;Event&gt; events) {<br>    Delivery delivery = null<br>    <br>    for (Event event: events) {<br>        switch (event.body()) {<br><br>            case DeliveryCreated created -&gt; {<br>                delivery = new Delivery();<br><br>                delivery.orderId = created.orderId();<br>                delivery.customerId = created.customerId();<br>                delivery.farmId = created.farmId();<br>                delivery.status = DeliveryStatus.CREATED;<br>                delivery.address = created.address();<br>                delivery.items = mapItems(created.items());<br>                delivery.deliveryCharge = created.deliveryCharge();<br>                delivery.total = created.total();<br><br>                Map&lt;String, String&gt; metadata = new HashMap&lt;&gt;();<br>                metadata.put(&quot;creationTimestamp&quot;, event.header().createdAt().toString());<br>                delivery.metadata = metadata;<br>            }<br>        }<br>    }<br>    return delivery;<br>}</pre><p>All subsequent events will mutate the Delivery object, but in a very limited way. For instance, the FoodWasPickedUp event contains only two pieces of information: orderId and what happened from a business perspective, which is represented by the class name:</p><pre>public record FoodWasPickedUp(String orderId) implements DomainEventBody {}</pre><p>Going back to the event replaying loop — if an event is of the FoodWasPickedUp type, the delivery status is changed to FOOD_PICKED and the metadata is updated:</p><pre>    public static Delivery from(List&lt;Event&gt; events) {<br>        Delivery delivery = // existing object recreated from previous events<br>        <br>        for (Event event: events) {<br>            switch (event.body()) {<br><br>                case FoodWasPickedUp foodWasPickedUp -&gt; {<br>                    var metadata = delivery.getMetadata();<br>                    metadata.put(&quot;foodPickedUpTimestamp&quot;, event.header().createdAt().toString());<br><br>                    delivery.metadata = metadata;<br>                    delivery.status = DeliveryStatus.FOOD_PICKED;<br>                }<br>            }<br>        }<br>        return delivery;<br>    }</pre><p>The same applies to all other events, which either change the status of a delivery or one of its properties. For instance, adding a tip to a delivery results in changing the tip value and the overall cost of the delivery:</p><pre>public static Delivery from(List&lt;Event&gt; events) {<br>    Delivery delivery = // existing object recreated from previous events<br>    <br>    for (Event event: events) {<br>        switch (event.body()) {<br><br>            case TipAddedToDelivery tipAddedToDelivery -&gt; {<br>                delivery.tip = tipAddedToDelivery.tip();<br>                delivery.total = tipAddedToDelivery.total();<br>            }<br>        }<br>    }<br>    return delivery;<br>}</pre><p>And that’s it. Essentially, this is how the current state of an entity can be rebuilt from events.</p><p>Are there any other ways to achieve this? Certainly. For instance, the entire method could be extracted from a domain object and moved to a separate factory class:</p><pre>public class DeliveryFactory {<br><br>    public static Delivery from(List&lt;Message&gt; events) {<br>        Delivery delivery = null;<br>        for (Message event: events) {<br>            switch (event.body()) {<br>                case DeliveryCreated created -&gt; {<br>                    delivery = new Delivery();<br><br>                    delivery.setOrderId(created.orderId());<br>                    delivery.setCustomerId(created.customerId());<br>                    delivery.setFarmId(created.farmId());<br>                    delivery.setStatus(DeliveryStatus.CREATED);<br>                    delivery.setAddress(created.address());<br>                    delivery.setItems(mapItems(created.items()));<br>                    delivery.setDeliveryCharge(created.deliveryCharge());<br>                    delivery.setTotal(created.total());<br><br>                    Map&lt;String, String&gt; metadata = new HashMap&lt;&gt;();<br>                    metadata.put(&quot;creationTimestamp&quot;, event.header().createdAt().toString());<br>                    delivery.setMetadata(metadata);<br>                }<br><br>                case DeliveryManAssigned deliveryManAssigned -&gt; {<br>                    delivery.setDeliveryManId(deliveryManAssigned.deliveryManId());<br>                }<br><br>                // all other cases<br><br>                default -&gt; throw new IllegalStateException(&quot;Failed to replay events to build delivery object. Unhandled events: &quot; + event.body().getClass());<br>            }<br>        }<br>        return delivery;<br>    }<br>}</pre><p>The problem with this approach is that it requires creating setters for each property of an object, which opens up the object to be modified in other parts of the code. If we want our domain object to adhere to Domain Driven Design principles, rather than being a simple Data Transfer Object, this can’t happen. Modification of any property of an object must go through special business methods in Delivery that protect business rules. Methods such as:</p><pre>public class Delivery {  <br><br>    public void assignDeliveryMan(String deliveryManId) {<br>        if (this.deliveryManId != null) {<br>            throw new DeliveryException(format(&quot;Failed to assign delivery man to an &#39;%s&#39; order. There is already a delivery man assigned with an orderId %s&quot;, orderId, this.deliveryManId));<br>        }<br><br>        if (List.of(DeliveryStatus.CANCELED, DeliveryStatus.FOOD_PICKED, DeliveryStatus.FOOD_DELIVERED).contains(status)) {<br>            throw new DeliveryException(format(&quot;Failed to assign a delivery man to an &#39;%s&#39; order. It&#39;s not possible do it for a delivery with &#39;%s&#39; status&quot;, orderId, status));<br>        }<br><br>        this.deliveryManId = deliveryManId;<br>    }<br>}</pre><p>Alright, so now that you know how to implement <em>event sourcing</em> and have an idea of what the implementation could look like, it might seem like the logical next step to select the approach you like the most, code it, and push it to production, right? I mean, with so many software engineering gurus advocating for it, what possibly could go wrong?</p><h3>When not to use it?</h3><p>While <em>event sourcing</em> may sound great, there are certain aspects of it that need to be carefully considered.</p><p>For example, a large number of events for a single business entity can lead to performance issues. Replaying thousands of events to obtain the current state of an entity may take a considerable amount of time, even if each event processing method is relatively fast. To address this problem, there are a couple of approaches, such as creating snapshot events (with the full state of an entity) and replaying events starting from them. Another approach involves creating a projection of the current state of an entity, where the current state is stored in a database alongside events. However, it’s important to note that projections should be disposable because events should remain the source of truth.</p><p>Additionally, one reason not to use <em>event sourcing</em> is its limited adoption. Not every developer has experience with it, and in situations where the business is pushing for the addition of new capabilities to a system, it may be safer to rely on familiar tools and approaches.</p><p>Furthermore, like every tool, <em>event sourcing</em> is not a one-size-fits-all solution. While it provides valuable information for analytics and tracking system activity, it’s not always necessary. For some domains, a traditional approach may suffice.</p><p>Lastly, implementing <em>event sourcing</em> requires specific tools to function properly. While the database layer may seem simple in theory, it needs to meet certain criteria, such as supporting concurrent writes and maintaining an append-only storage model. This introduces various challenges, such as handling different versions of the same events, compensating for incorrect events, and managing the removal of personal information (the right to be forgotten). These complexities may deter some from adopting <em>event sourcing</em>.</p><h3>And when to use it?</h3><p>So far, I’ve covered what <em>event sourcing</em> is and discussed a few reasons why you might hesitate to use it. But let’s turn things around because there are plenty of compelling reasons to embrace it.</p><p>One major advantage of <em>event sourcing</em> is its ability to store the time of a business event. This data, often overlooked in traditional approaches, can be invaluable for advanced analytics. Events not only drive the processing part of a system but also serve as a goldmine for analytics. For instance, they can answer important business questions like how long a customer waited for a delivery or which step of an order process took the most time. Such insights can lead to better user experiences and informed decision-making.</p><p>Moreover, events can be a game-changer in highly regulated industries such as healthcare and finance. They allow the construction of a comprehensive log of actions, enabling auditors to identify malicious activities within the system. However, it’s essential to exercise caution with this capability, as events should strike a balance between granularity and providing sufficient context for auditors.</p><p>Keeping a log of events also benefits operational teams by facilitating error detection and resolution. When an issue arises, such as a broken order, a quick review of the events log can often pinpoint the actions that led to the problem. Furthermore, teams can easily revert malicious events to restore an entity to its previous state.</p><p>From a development perspective, <em>event sourcing</em> offers flexibility and agility. Storing only facts about entities eliminates the need to adhere strictly to a specific business model. If the initial domain model doesn’t align with business needs and requires changes, there’s no need for extensive data migration. Simply adjust how events are replayed to rebuild the entity. Additionally, the same set of events can serve as the foundation for building different projects, such as distinct views of an order from the perspectives of customers and sellers.</p><p>Another boon for development teams is the aid <em>event sourcing</em> provides in debugging. A continuous stream of events allows developers to navigate back and forth, enabling what is often referred to as “time travel” debugging. This capability provides invaluable insights for identifying and fixing bugs efficiently.</p><h4>Example of Event sourcing systems</h4><p><em>Event sourcing</em> proves valuable across a wide range of use cases. For instance:</p><ul><li><strong>Account ledger</strong> — This is a classic and natural application of <em>event sourcing</em>. An account ledger maintains a log of all incoming and outgoing transactions, forming the basis for calculating the account balance. Some banks even leverage event data to provide insights into spending habits, categorizing expenses into groups such as rent, food, or entertainment. All of it are different projections and are based on the same data — transaction events.</li><li><strong>Hospital inventory</strong> — Hospitals rely on meticulous inventory management to ensure patients receive correct medication dosages at the right times. Additionally, maintaining control over drug supplies is crucial for regulatory compliance and legal purposes. <em>event sourcing</em> facilitates this by recording events of medical consumption, detailing which patient took which medication, when, and by whom it was dispensed.</li><li><strong>Ordering system</strong> — <em>Event sourcing</em> finds extensive application in e-commerce ordering systems. By logging events related to order processing and fulfillment, businesses can not only reconstruct the order’s state but also derive insights for optimizing the ordering and delivery processes. Essentially, any system operating as a state machine stands to benefit from <em>event sourcing</em>.</li></ul><h3>Event sourcing in application</h3><p>Now that we understand <em>event sourcing</em>, let’s delve into its implementation in application code. The basic flow for each use case can be straightforwardly encapsulated within a Service class responsible for handling it. The essence of each method&#39;s flow can be summarized into a few key steps:</p><ol><li>Accept incoming arguments, typically in the form of a command.</li><li>Retrieve events for an entity from an <em>event store</em>.</li><li>Replay events to rebuild the entity’s state.</li><li>Invoke a business method on the entity.</li><li>Create the resulting event.</li><li>Store the resulting event.</li></ol><p>This general approach can be translated into code as follows:</p><pre>class Service {<br><br>    private final EventStore eventStore;<br><br>    public void handle(Command command) {<br><br>        List&lt;Event&gt; storedEvents = retrieveEvents(command.entityId)<br>        Entity entity = Entity.from(storedEvents);<br><br>        entity.handle(command.newState);<br><br>        Event resultingEvent = new NewEntityStateEvent(entity.id);<br>        eventStore.store(resultingEvent);<br>    }<br><br>    private List&lt;Event&gt; retrieveEvents(String streamId) {<br>        var storedEvents = eventStore.getEventsFor(streamId);<br>        if (storedEvents.size() == 0) {<br>            throw new EventStoreException(&quot;There are no events for stream: &quot;, streamId);<br>        }<br>        return storedEvents;<br>    }<br>}</pre><p>Let’s consider a more concrete example of a DeliveryService responsible for handling a PickUpFood command:</p><pre>class DeliveryService {<br><br>    private final EventStore eventStore;<br><br>    public void handle(PickUpFood pickUpFood) {<br><br>        List&lt;Event&gt; storedEvents = retrieveEvents(pickUpFood.orderId())<br>        var delivery = Delivery.from(storedEvents);<br><br>        delivery.pickUpFood();<br><br>        Event resultingEvent = new FoodWasPickedUp(delivery.orderId());<br>        eventStore.store(resultingEvent);<br>    }<br><br>    private List&lt;Event&gt; retrieveEvents(String orderId) {<br>        var storedEvents = eventStore.getEventsFor(orderId);<br>        if (storedEvents.size() == 0) {<br>            throw new EventStoreException(&quot;There are no events for order: &quot;, orderId);<br>        }<br>        return storedEvents;<br>    }<br>}</pre><p>If we aim to ensure that every invocation of a business method on a domain object results in the generation of an event, we can shift the responsibility of event generation to the domain object itself:</p><pre>class Delivery {<br><br>    private String orderId;<br><br>    public Event pickUpFood() {<br>        if (status != DeliveryStatus.FOOD_READY) {<br>            throw new DeliveryException(format(&quot;Failed to set food as picked up for an &#39;%s&#39; order. It&#39;s not possible do it for a delivery with &#39;%s&#39; status&quot;, orderId, status));<br>        }<br>        this.status = DeliveryStatus.FOOD_PICKED;<br>        return new FoodWasPickedUp(orderId);<br>    }<br>}</pre><p>As a result, the code in the DeliveryService can be simplified:</p><pre>class DeliveryService {<br><br>    public void handle(PickUpFood pickUpFood) {<br><br>        List&lt;Event&gt; storedEvents = retrieveEvents(pickUpFood.orderId())<br>        var delivery = Delivery.from(storedEvents);<br><br>        Event resultingEvent = delivery.pickUpFood();<br><br>        eventStore.store(resultingEvent);<br>    }<br>}</pre><p>In my opinion, this approach is far superior. By moving event creation to the domain object, which encapsulates business logic, we ensure that every action taken results in the generation of an event-a factual statement of what just occurred. If event creation were to remain in the service layer, there’s a risk of forgetting to create the corresponding event. Additionally, there are situations where event creation requires information from private fields within the domain object, which may not be accessible to the service.</p><p>Another approach, similar to the previous one, involves temporarily storing resulting events within the domain object itself. Again, the events would be created within the domain object, but instead of being returned after the method is invoked, they would be added to a list of events maintained by the domain object:</p><pre>class Delivery {<br><br>    private String orderId;<br>    List&lt;Event&gt; changes = new ArrayList&lt;Event&gt;(); <br><br>    public void pickUpFood() {<br>        if (status != DeliveryStatus.FOOD_READY) {<br>            throw new DeliveryException(format(&quot;Failed to set food as picked up for an &#39;%s&#39; order. It&#39;s not possible do it for a delivery with &#39;%s&#39; status&quot;, orderId, status));<br>        }<br>        this.status = DeliveryStatus.FOOD_PICKED;<br>        changes.add(new FoodWasPickedUp(orderId));<br>    }<br><br>    public List&lt;Event&gt; uncommittedChanges() {<br>        return changes;<br>    }<br>}</pre><p>In that case, the flow of the handling method in the DeliveryService might resemble the following:</p><pre>class DeliveryService {<br><br>    public void handle(PickUpFood pickUpFood) {<br><br>        List&lt;Event&gt; storedEvents = retrieveEvents(pickUpFood.orderId())<br>        var delivery = Delivery.from(storedEvents);<br><br>        delivery.pickUpFood();<br><br>        eventStore.store(delivery.uncommittedChanges());<br>    }<br>}</pre><p>This flow can prove beneficial, especially when multiple business methods of the domain object are invoked within a single service method. By deferring the collection of events until the end of the method, we streamline the process and commit them in one go.</p><p>Now, let’s introduce the last component: the EventStore. Since this article revolves around <em>event sourcing</em>, here&#39;s a simple definition of the EventStore interface:</p><pre>public interface EventStore {<br><br>    List&lt;Event&gt; getEventsFor(String streamId);<br>    <br>    void store(Event event);<br>    <br>    default void store(List&lt;Event&gt; events) {<br>        events.forEach(this::store);<br>    }<br>}</pre><p>And a simple in-memory implementation:</p><pre>public class InMemoryEventStore implements EventStore {<br><br>    Map&lt;String, List&lt;Event&gt;&gt; store = new ConcurrentHashMap&lt;&gt;();<br><br>    @Override<br>    public void store(Event event) {<br>        var stream = store.getOrDefault(event.orderId(), new ArrayList&lt;&gt;());<br>        stream.add(event);<br>        store.put(event.orderId(), stream);<br>    }<br><br>    @Override<br>    public List&lt;Event&gt; getEventsFor(String streamId) {<br>        return store.getOrDefault(streamId, List.of());<br>    }<br>}</pre><p>In real-world scenarios, the implementation of an <em>event store</em> can vary widely. Events may be persisted in relational or non-relational databases, each offering its own set of advantages and trade-offs. In one of my upcoming posts, I’ll delve into the implementation of an <em>event store</em> using <em>PostgreSQL</em>.</p><h3>Summary</h3><p><em>Event sourcing</em> is a powerful technique that not only preserves information about domain objects but also records the time of each action that led to a given state. This additional dimension of information can be incredibly valuable for advanced analytics, improved error handling, and facilitating adjustments to the business model.</p><p>I hope this article has provided you with some insights into <em>event sourcing</em>. For further information, check out the <em>References</em> section. Additionally, if you’re interested in the code mentioned in this post, you can find it in my repository on GitHub.</p><p><a href="https://github.com/wkrzywiec/farm-to-table">GitHub - wkrzywiec/farm-to-table</a></p><h3>References</h3><ul><li><a href="https://event-driven.io/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events? - Event-Driven.io</a></li><li><a href="https://www.eventstore.com/event-sourcing">Beginner&#39;s Guide to Event Sourcing | Event Store</a></li><li><a href="https://martinfowler.com/eaaDev/EventSourcing.html">Event Sourcing</a></li><li><a href="https://event-driven.io/en/never_lose_data_with_event_sourcing/">Never Lose Data Again - Event Sourcing to the Rescue! - Event-Driven.io</a></li><li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/workshops/introduction-to-event-sourcing">EventSourcing.JVM/workshops/introduction-to-event-sourcing at main · oskardudycz/EventSourcing.JVM</a></li><li><a href="https://github.com/eugene-khyst/postgresql-event-sourcing">GitHub - eugene-khyst/postgresql-event-sourcing: A reference implementation of an event-sourced system that uses PostgreSQL as an event store built with Spring Boot. Fork the repository and use it as a template for your projects. Or clone the repository and run end-to-end tests to see how everything works together.</a></li></ul><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/052_event-sourcing-part-1/"><em>https://wkrzywiec.is-a.dev</em></a><em> on June 10, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5b6bbacb2713" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Start learning JVM internals with Grafana dashboard]]></title>
            <link>https://wkrzywiec.medium.com/start-learning-jvm-internals-with-grafana-dashboard-a07be9eeb2ea?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/a07be9eeb2ea</guid>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[garbage-collection]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[jvm]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Sat, 13 Jan 2024 00:00:43 GMT</pubDate>
            <atom:updated>2024-01-21T20:09:40.223Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Are you a Java developer who knows the ins and outs of programming in Java but finds the world of JVM internals a bit mystifying? You’re not alone. Understanding what goes on under the hood of the Java Virtual Machine (JVM) can seem like a daunting task. In this blog post, I’ll demystify basic JVM internals concepts using insights from a popular Grafana dashboard. Buckle up, we’re going on an adventure into the JVM world!</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AxiwIUQu5o9ALz-c.jpg" /><figcaption><em>Photo generated with </em><a href="https://leonardo.ai/"><em>Leonardo Ai</em></a></figcaption></figure><h3>Not enough memory</h3><p>Learning a programming language can be a challenging journey. First, you need to grasp the basic syntax to accomplish standard tasks. The next step involves learning one or two popular frameworks within a given ecosystem to enhance your visibility in the job market. However, once you secure that dream job, you quickly realize that having a solid foundation in a programming language and popular frameworks is not sufficient. Elements like best practices, design patterns, and system design play crucial roles in becoming a successful expert in software development.</p><p>Finally, a day arrives when you have mastered all this knowledge (or at least you think you have), and <strong>you receive a call from the operations team stating that your application is experiencing overwhelming success but struggles to handle the large influx of user requests</strong>. None of the previous steps prepared you for the skyrocketing CPU usage or memory issues. Panic sets in as you desperately try to recall why these components are essential and how they relate to your state-of-the-art application.</p><h3>Look into inside</h3><p>Does it sound familiar?</p><p>How could you prevent this from happening? The first step is to come to the realization that you don’t know everything about your piece of software. Yes, you have coded it, but do you know how it behaves in the wild production environment? If you received a similar call, you probably don’t know. Therefore, the next step for you would be to <strong>initiate app monitoring to observe trends and receive alerts when key metrics approach dangerous thresholds</strong>. Additionally, understanding how the app responds under various types of pressure is crucial.</p><p>Numerous tools can be employed for this purpose. Some are built into the JDK, such as <em>Java Flight Recorder</em> with <em>Java Mission Control</em>, while others can be downloaded, like <em>JProfiler</em>, and some are cloud platforms, such as <em>Datadog</em>. It’s easy to feel overwhelmed initially, as each tool provides different types of information, often at a very low level.</p><p>So, where do you begin with monitoring app performance? Which tool should you choose first?</p><h3>Grafana dashboard</h3><p><strong>I would recommend starting with <em>Prometheus</em> and <em>Grafana</em></strong>. Both tools are open source and non-invasive, meaning there’s no need to add any special tool/agent that may affect overall performance. They only require adding metrics libraries to a project. The setup is relatively straightforward, as I’ve outlined in a previous article — <a href="https://medium.com/@wkrzywiec/how-to-set-up-monitoring-tools-for-java-application-322d14c191e4">How to set up monitoring tools for JVM</a>.</p><p>The mentioned post also covers how to import one of the most popular JVM dashboards — <a href="https://grafana.com/grafana/dashboards/4701-jvm-micrometer/"><strong>JVM (Micrometer)</strong></a> — which will be the subject of this post. It will walk you through key metrics and explain when and why they’re important.</p><p>The dashboard consists of sections that will be described in the following parts, which I’ve grouped and changed the order a little bit.</p><h3>JVM Memory</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6OTIWsM5iL2MXuuz.png" /></figure><p>If you ever wonder what’s <strong>the first thing that you must keep eye on when monitoring Java application in most of the case it would be the memory usage</strong>. This is a vital information about the health of an app, since JVM needs it to run it.</p><p>Therefore it is not a suprise thet this is a first information that is provided by the dashboard — the percentage of <strong>how much heap and non-heap memory is fillied up</strong> (along with data about how long the app is running).</p><p>In most applications, these two metrics are the ones we should keep an eye on. They have a significant impact on its performance and can quickly explain why the app is running slow or even crashing.</p><p>Why these two metrics are that important? First of all we need to know that Java is object-oriented language. It means that every program is a set of objects which interact with each other. They have methods that are invoked by other objects and they also hold data. And in the real applications number of objects may be very large. And that’s why it is worth to monitor their number and size with the first indicator. The latter is accountable for measuring memory use of non-object related things, which in some cases are also worth to monitor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*I2b6KRwDkjJZe3N2.png" /></figure><p>Moving on, let’s examine the charts above, which illustrate how memory usage has changed over time. The charts depict the actual memory usage ( used) at a given moment, the maximum available ( max), and the memory guaranteed to be available for the JVM ( committed).</p><p>What can we read from the Heap memory chart? Certainly, the used memory shouldn&#39;t reach the max value. <strong>A dangerous situation arises when the increasing amount of memory in the Heap area indicates that many objects are created and never destroyed</strong> (collected by the Garbage Collector, which will be mentioned later). If this happens, the app will start to act slower and eventually crash, throwing the java.lang.OutOfMemoryError.</p><p>In addition to Heap data, the JVM has a second data area called <strong>Non-Heap</strong> (or Off-Heap or Native Memory). It stores various data, which can be divided into the following spaces:</p><ul><li><strong>Metaspace (aka Method Area)</strong>: It holds class-related information about class code, their fields, and methods after being loaded by the classloader (more about it in the article). It also contains information about constants (in the Constant Pool).</li><li><strong>Code Cache</strong>: This area stores optimized native code produced by the Just-In-Time compiler.</li><li><strong>Program Counters</strong>: These point to the address of the currently executed instruction (each thread has its own Program Counter).</li><li><strong>Stacks</strong>: The Last-In-First-Out (LIFO) stack of frames for each executed method. It contains primitive variables and references to the objects on the heap.</li></ul><p>While it is typically the heap that may be too small, we need to understand that memory problems with non-heap data could also crash an app. Too many classes loaded into Metaspace may cause a java.lang.OutOfMemoryError, and too many frames in the Stack may result in a java.lang.StackOverflowError being thrown.</p><h4>JVM Memory Pools (Heap)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XMsw9WFzoxsaH-Vj.png" /></figure><p>Let’s now delve into Heap usage. But first, we need to understand how the Heap is structured because it is not a monolith. <strong>The Heap is divided into various spaces</strong>, the number of which depends on the type of Garbage Collector used. Above, we can see three charts for <em>Eden Space</em>, <em>Survivor Space</em>, and <em>Old Gen</em>, which are used by the <strong>G1</strong> Garbage Collector.</p><p>But why is the Heap split into spaces? It comes from something called the <strong>weak generational hypothesis</strong>. This hypothesis presumes that the lifespan of objects varies. Most of them are very short-lived, used only once, and then the memory they occupy can be freed. Others, usually a smaller fraction, are the ones that are constantly used and cannot be destroyed. Due to this fact, the Garbage Collector (GC) splits the heap into generations (spaces), where short-lived objects reside in young generations ( <em>Eden Space</em> and <em>Survivor Space</em>), and long-lived objects in an old generation ( <em>Old Gen</em>).</p><p><strong>Having the heap split into generations allows the GC to avoid performing one long garbage collection on the entire heap. Instead, the GC performs it only on a subset at a time.</strong> It also allows for different collection frequencies in each generation; the <em>Eden Space</em> is cleaned more often than the <em>Old Gen</em> because objects located there are needed only for a short period.</p><p>The above screenshot confirms this. We can see that the plot for used memory in <em>Eden Space</em> is formed in a characteristic jigsaw pattern. It quickly fills up with new objects (expressed as a rising plot on the chart), and once it reaches a certain threshold, it is emptied (causing the plot to go down).</p><p>On the contrary, the <em>Survivor Space</em> and <em>Old Gen</em> are much more stable. There are some changes in them, but they are very subtle.</p><p>These three charts give us vital information about the content of the heap. We can read how often new objects are created and removed. If the cleanup for them is done very often and the old objects are kept at a relatively same level, it is tempting to increase the ratio of young to old generation size. The larger the young generation is, the less often cleanup will be done. But on the other hand, it will be bigger, so the cleanup may take a little bit longer. The same goes for old generations; the smaller it gets, the more frequently it is cleaned. So we must keep a balance of it.</p><p>To adjust it, use -XX:NewRatio=N (which is a ratio of young and old generations, e.g., N=2 means that the young generation is twice as big as the old one) or -XX:NewSize=N (which is the initial size of the young generation - all remaining will be assigned to the old generation).</p><h4>What if the app is consuming too much memory?</h4><p>The heap is one of the most crucial memory areas for JVM and typically requires a substantial amount of space. If it approaches the maximum threshold, <strong>the simplest solution is to increase heaps’ maximum size</strong>. A larger heap ensures that all necessary objects can fit into it. This can be accomplished by specifying -Xmx followed by the size in megabytes allocated for the heap. For example, -Xmx1024m will allocate 1024 MB for the heap. It&#39;s easy and straightforward.</p><p>While adjusting the maximum size of the heap may provide a temporary solution, in many cases, it merely masks the symptoms rather than addressing the root cause of the problem. <strong>An increasing number of objects could indicate a memory leak within the application, requiring identification and resolution in the code</strong>.</p><p>Another possibility is that <strong>an excessive amount of data is being loaded into memory, either due to an inefficient data structure or an unexpectedly large volume of it</strong>. In the former case, optimizing how data is stored in the JVM is the solution, while in the latter, considerations such as increasing the heap size or chunking the large amount of data into smaller pieces.</p><p>But how to spot if we have inefficient data structure? There are couple of signs and one of them is using objects with lots of fields (e.g. entities) from which only a small subset is used. <strong>Limiting the number of fields per task can be beneficial here. Also we need to remember that even if fields won’t be initialized they also consume memory!</strong></p><p>Additionally, <strong>strive to avoid using objects instead of primitives</strong>. Many instances exist where objects contain fields like Boolean instead of plain boolean. While this may seem like a minor change, it can significantly impacts memory usage. Similarly, treating String objects as containers for numbers or booleans variables consumes more memory than necessary.</p><p>Another optimization to reduce memory footprint involves <strong>refraining from initializing </strong><strong>String variables with identical values repeatedly</strong>.</p><p>In certain situations, it can be highly beneficial to avoid creating immutable objects. This is particularly true when new objects need to be created as copies of others. Of course, I’m not discouraging the use of immutable objects altogether, as they prove invaluable in many scenarios. However, for objects created and used within a small method once or twice, it is sometimes more advantageous to make them mutable.</p><p>These suggestions only scratch the surface of addressing memory-related issues. Numerous resources are available to guide you in further optimizing memory usage.</p><h4>Garbage Collection</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iCSjr86_njYCBFLA.png" /></figure><p>The previous section already mentioned the Garbage Collector, which we will now examine more closely. It is a key component of the JVM because, in contrast to languages like C++, we, as developers, don’t need to worry about freeing memory once an object is no longer needed. <strong>The role of the GC is to decide which objects should be destroyed and which should be preserved.</strong></p><p>All new objects are initially allocated in the <em>Eden Space</em>. However, during the next garbage collection, they are promoted to one of the <em>Survivor Spaces</em> if they are still in use. Subsequently, during subsequent garbage collections, they are again checked to determine whether they should be promoted to the next subspace of the <em>Survivor Space</em> or even to the <em>Old Gen</em>.</p><p>The first plot in the above screenshot, <strong>Collections</strong>, tells us how often a certain type of garbage collection occurs in a second. We can see that only one minor garbage collection (cleaning only the young generation of the heap) is happening, but it also shows a major garbage collection (cleaning objects from the old generation). <strong>By reading this chart, we can figure out how often each type of collection is occurring, and if it’s too frequent, it may suggest that one of the spaces is filling up too quickly with a large number of objects.</strong></p><p>However, even if the number of collection occurrences is relatively high, it’s not always a cause for concern. If each one of them takes a very short time and does not significantly affect the overall application performance, we have nothing to worry about. To ensure that this is the case, we can look into the second plot, <strong>Pause Durations</strong>, which provides information about the average time of each garbage collection.</p><p>The last plot, <strong>Allocated/Promoted</strong>, shows us how much memory was used to allocate objects in the young generation and how much of it was promoted to older generations. This information is really helpful when we want to understand the memory load that GC needs to deal with in each cycle.</p><p>After identifying an issue in one of these graphs, we can take two approaches: tune JVM memory limits and GC behavior or select a different GC. Depending on the Java version and distribution (e.g., Oracle’s HotSpot, Amazon Corretto, Azul Zulu, or Eclipse Adoptium), the list of available GCs is different, but most of them can be categorized into:</p><ul><li><strong>Generational</strong> vs. <strong>non-generational</strong> — Some GCs may not split a heap into generations but treat it as a single memory space. However, usually it is not the case (though the approach varies among distributions).</li><li><strong>Stop-the-world</strong> vs. <strong>concurrent</strong> — GCs in the first group pauses the entire application for cleanup, while the latter performs cleanup while the application is still running (or, to be precise, they briefly stop the application).</li><li><strong>Single-thread</strong> vs. <strong>multi-thread</strong> — The first group operates using only one thread, while the latter utilizes at least two.</li><li><strong>Incremental</strong> vs. <strong>monolithic</strong> — This is specific to stop-the-world GCs and indicates whether garbage collection is done only for a subset of the generation or until it’s complete.</li></ul><p>Here is a list of available GCs in the Amazon Corretto distribution of OpenJDK, one of the most popular distributions according to the <a href="https://newrelic.com/sites/default/files/2023-04/new-relic-2023-state-of-the-java-ecosystem-2023-04-20.pdf">2023 State of the Java Ecosystem</a> for 21 version of Java:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ce0e05b882c2cf597b0de258c3c347df/href">https://medium.com/media/ce0e05b882c2cf597b0de258c3c347df/href</a></iframe><p>The above table lists all available GCs along with their basic properties. Even though some of them may seem better than others, it is crucial not to overlook trade-offs. For instance, the short garbage collection pauses of <em>Shenandoah</em> and <em>ZGC</em> come with the cost of higher CPU usage. Therefore, before selecting one, make sure you understand the price you would need to pay.</p><p>If you are looking for more information about each GC, check the <em>References</em> section, where I’ve included links to the official documentation of each one of them.</p><h3>JVM Memory Pools (Non-Heap)</h3><p>Apart from objects, JVM needs memory to store other things. This is represented by the next graphs from <em>JVM Memory Pools (Non-Heap)</em> in the discussed dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tgOsm4Pc2bABBGLe.png" /></figure><ul><li><strong>Metaspace (a.k.a. Method Area)</strong> — This is the place where metadata and definitions of every class are stored (in simpler terms, every .class file in the runtime representation). Additionally, <em>Metaspace</em> holds method counters used by the JIT compiler and a constant pool (a data structure to store information about references and constants associated with a class, such as static final fields). By default, there are no limits on how big it can get, but it can be tuned using the -XX:MaxMetaspaceSize flag. If not provided, it may occur that the JVM will consume too much memory and slow down the machine on which the application is running.</li><li><strong>Compressed Class Space</strong> — This is a subset of Metaspace with classes metadata.</li></ul><p>The remaining three graphs show segments of the code cache. It is used to store code optimized by the JIT (Just-In-Time) compiler. This compiler plays a vital role in JVM by identifying and optimizing places in the code that are executed very often. These hot spots (hence the name HotSpot for Oracle’s JDK distribution) are identified and recompiled from bytecode to machine (native) code. Along with other optimizations, such as method inlining or dead code elimination, it makes those parts of the code ultra-fast to execute, making Java applications robust.</p><ul><li><strong>CodeHeap ‘profiled nmethods’</strong> — This segment holds lightly optimized methods.</li><li><strong>CodeHeap ‘non-profiled nmethods’</strong> — This segment contains fully optimized methods.</li><li><strong>CodeHeap ‘non-nmethods’</strong> — The smallest segment that contains compiler buffers and the bytecode interpreter.</li></ul><p>Code cache is split into three segments:</p><p>This provides all the insights that the discussed dashboard gives us about the non-heap area. While these are valuable pieces of information, it’s crucial to be aware that it’s not everything. All mentioned data areas, like the heap and parts of the non-heap, are shared among all application threads. However, some areas are assigned to specific threads, such as the stack or program counter, and these are also vital parts of the JVM.</p><h3>Classloading</h3><p>Besides information about the amount of memory occupied by loaded classes, the dashboard also provides details on how many classes were loaded into memory and when these loadings occurred. This information can be gleaned from the first two charts:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*w1sf0vmAnhe4phSF.png" /></figure><p>Most of the class loading occurs during the application startup, especially for solutions utilizing Inversion of Control. This is when most objects need to be initialized, preceded by the loading of their definitions into the JVM process. However, not all classes are loaded immediately. Some class definitions are loaded lazily during runtime.</p><h3>JVM Misc</h3><p>Next section available in the dashboard govers various things worth monitoring.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HBhv2vYTj0kAT3Tt.png" /></figure><p>The name of the first chart, <strong>CPU Usage</strong>, suggests that it shows how the CPU is utilized. It presents three series: system (total CPU usage of the host), process (CPU usage for all JVM processes), and process-15m, which is an average of the latter gauge from the last 15 minutes.</p><p>The next graph, <strong>Load</strong>, provides more insight into the number of processes (load) running and queued for the CPU on average in 1 minute. If this indicator is below the number of available CPUs (also available on this chart), it means that there are no waiting processes due to occupied CPU, which is good news. Otherwise, it would indicate that the CPU can’t keep up with executing all processes.</p><p>The next two graphs provide information about JVM threads. The first one, <strong>Threads</strong>, displays the number of daemon threads, which are the opposite of user threads. Essentially, these two types of threads in the JVM are almost the same. The first difference is that when the program is stopped, first the user threads are halted, and then the daemon threads are exited. Only after that is the JVM considered as non-running. The second difference is that daemon threads serve the user thread. Typically, they are assigned to handle garbage collection, but they can also be utilized in our code by marking the Thread object with the setDaemon(true) methods.</p><p>The second graph, <strong>Thread States</strong>, presents the distribution of threads divided by their state.</p><p>The next row of charts starts with <strong>GC Pressure</strong>, indicating the percentage of CPU used for garbage collection. It’s essential to monitor this metric along with other garbage collection metrics mentioned earlier.</p><p>Another noteworthy metric to keep an eye on is the number of logs produced by the app, grouped by level, known as <strong>Log Events</strong>. This information can be valuable for spotting problems if the number of warn or error level logs is higher than usual, which may not be apparent in other metrics.</p><p>The last chart, <strong>File Descriptors</strong>, displays the number of files opened by the process.</p><h3>I/O Overview</h3><p>The last section provides data not directly connected with JVM, but it’s crucial to keep an eye on, especially if it provides a REST API (which is the case for most Java applications nowadays).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IaIY0tNRsTBVbSz3.png" /></figure><p>The first chart shows how many HTTP requests per second were made. The next one provides the same information, but this time narrowed down to all 5xx responses. A significant increase in 5xx responses may indicate unwanted issues. Also, in the case of high user traffic (as indicated in the first chart), it’s worth monitoring how much memory is consumed by the app, how often garbage collection is performed, or other indicators mentioned earlier. All of these factors may be correlated and affect how fast users receive a response from your service.</p><p>The next indicator measures how much time, on average and at maximum, HTTP requests took (excluding 5xx responses). A sudden increase in this metric may indicate that garbage collection pauses are taking longer than usual, which is worth checking in that case.</p><p>The last chart provides information about Tomcat’s (or Jetty’s) busy and active threads. It shows how many of them are currently in use, which is crucial for understanding the throughput of an application. The chart also displays information about the configured maximum number of Tomcat’s threads. If the number of currently used threads is very close to this limit, it should serve as a warning that the application may not be able to handle all incoming requests.</p><h3>Conclusion</h3><p>And there you have it! 🚀 We’ve journeyed through the entire JVM (Micrometer) Grafana dashboard and soaked in a ton of insights about the JVM and its crucial indicators. 🧠✨ I hope that now the JVM feels more like a friendly companion than a mysterious black box. If you’re hungry for more knowledge, check out the references mentioned below or dive into tools like Java Flight Recorder or profilers. 🛠️ Who knows, maybe they’ll make an appearance on this blog someday!</p><p>Until then, why not set up the Grafana dashboard for your project and start unveiling the secrets of how your software dances in the production environment? Happy monitoring! 🎉👩‍💻👨‍💻</p><h3>References</h3><ul><li><a href="https://www.oreilly.com/library/view/java-performance-the/9781449363512/">Java Performance: The Definitive Guide</a></li><li><a href="https://www.amazon.com/Java-Garbage-Collection-Mini-Book/dp/1329312384">The Java Garbage Collection Mini-Book</a></li><li><a href="https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html">Getting Started with the G1 Garbage Collector</a></li><li><a href="https://docs.oracle.com/en/java/javase/21/gctuning/available-collectors.html#GUID-45794DA6-AB96-4856-A96D-FDE5F7DEE498">HotSpot Virtual Machine Garbage Collection Tuning Guide</a></li><li><a href="https://docs.oracle.com/en/java/javase/21/gctuning/parallel-collector1.html">HotSpot Virtual Machine Garbage Collection Tuning Guide</a></li><li><a href="https://wiki.openjdk.org/display/zgc/Main">Main</a></li><li><a href="https://wiki.openjdk.org/display/zgc/Main">Main</a></li></ul><p><a href="https://inside.java/2023/11/28/gen-zgc-explainer/">https://inside.java/2023/11/28/gen-zgc-explainer</a></p><ul><li><a href="https://wiki.openjdk.org/display/shenandoah/Main">Main</a></li><li><a href="https://stuefe.de/posts/metaspace/what-is-metaspace/">What is Metaspace?</a></li></ul><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/051_grafana-analysis/"><em>https://wkrzywiec.is-a.dev</em></a><em> on January 13, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a07be9eeb2ea" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prepare Test Data Quicker with Test Data Builder!]]></title>
            <link>https://wkrzywiec.medium.com/prepare-test-data-quicker-with-test-data-builder-a3a8e0efd479?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3a8e0efd479</guid>
            <category><![CDATA[test-data-management]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[craftsmanship]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Sun, 08 Oct 2023 00:00:20 GMT</pubDate>
            <atom:updated>2023-10-15T11:50:09.570Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Many of us have been in a situation where writing application code is quick, easy, and enjoyable. However, when it comes to test code, it often turns out to be time-consuming, tedious, and boring. There can be several reasons for this, one of which is the necessity of preparing a set of test data. In some extreme cases, this data preparation may consume a significant portion of the test body. This not only becomes inconvenient when we only want to test a small part of the code but also decreases the overall readability of the test. In this post, I’ll demonstrate how this issue can be simplified using Test Data Builder.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0n070bjjeyJAKK4m" /><figcaption>Photo by <a href="https://unsplash.com/@therachelstory?utm_source=medium&amp;utm_medium=referral">Rachel Park</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Preparing test data is boring and tedious!</h3><p>Let’s be honest, how familiar does this code look to you?</p><pre>class DeliveryTest {<br><br>  @Test<br>  void delivery_is_canceled_if_was_just_created() {<br>    //given<br>    Food pizza = new Food(&quot;Pizza Margherita&quot;, 1);<br>    Food coke = new Food(&quot;Coke&quot;, 1);<br>    Delivery delivery = new Delivery(UUID.randomUUID(), List.of(pizza, coke), &quot;Main Street 1, Naples&quot;)    <br>    <br>    //when<br>    delivery.changeStatus(CANCELED)    <br>    <br>    //then<br>    assertEquals(CANCELED, delivery.status());<br>  }<br>}</pre><p>Even for a seemingly simple test case, we must create the Delivery class with all its necessary fields. Now, envision if this class were significantly more complex, perhaps requiring an address represented by a separate class (possibly with GPS coordinates); it would swiftly become challenging to manage.</p><p>Furthermore, it would complicate the readability and comprehension of the code. Why should we input an address when the sole purpose of the test is to validate the cancellation of a delivery? Our tests should clearly convey their intent and the preconditions required to fulfill them.</p><p>One way to tackle these problems is to move all setup steps into a private method so that it is not only encapsulated in an elegant method but also can be reused across other test cases.</p><pre>class DeliveryTest {<br><br>  @Test<br>  void delivery_is_canceled_if_was_just_created() {<br>    //given<br>    Delivery delivery = aNewDelivery();  <br>    <br>    //when<br>    delivery.changeStatus(CANCELED);  <br>    <br>    //then<br>    assertEquals(CANCELED, delivery.status());<br>  }<br><br>  private Delivery aNewDelivery() {<br>    Food pizza = new Food(&quot;Pizza Margherita&quot;, 1);<br>    Food coke = new Food(&quot;Coke&quot;, 1);<br>    return new Delivery(UUID.randomUUID(), List.of(pizza, coke), &quot;Main Street 1, Naples&quot;);<br>  }<br>}</pre><p>Such approach solves the problem of tedious work for test setup but only partially. What if each test requires a slightly different setup? Arguments can be added to this method to make it more customizable, but in a very large set of tests, the number of arguments may quickly become very big and hard to maintain.</p><pre>private Delivery aNewDelivery(String address, List&lt;Food&gt; foods) {<br>  return new Delivery(UUID.randomUUID(), foods, address);<br>}</pre><p>Is there a different approach to it? Is there a way to avoid all this setup and at the same time give a flexibility for adjustments of our test data?</p><h3>So let’s use the magic tool…</h3><p>Let’s introduce a Test Data Builder object which will be responsible for generating test data.</p><pre>class DeliveryTest {<br><br>  @Test<br>  void delivery_is_canceled_if_was_just_created() {<br>    //given<br>    Delivery delivery = DeliveryTestData.aNewDelivery().entity();  <br>    <br>    //when<br>    delivery.changeStatus(CANCELED);  <br>    <br>    //then<br>    assertEquals(CANCELED, delivery.status());<br>  }<br>}</pre><p>It is somehow easier to read if we compare with a first approach, but it looks a little bit off with this entity() at the end. Without it would look pretty close to the second approach. Where is a gain here then?</p><p>Let’s have another test example to see the benefit of using Test Data Builder:</p><pre>class DeliveryTest {<br><br>  @Test<br>  void delivery_can_not_be_completed_if_was_canceled() {<br>    //given<br>    Delivery delivery = aDelivery()<br>                            .withStatus(CANCELED)<br>                            .entity();  <br>    <br>    //when &amp; then<br>    assertThrows(<br>        IllegalStateException.class, <br>        () -&gt; delivery.changeStatus(COMPLETED));<br>  }<br>}</pre><p>Here, the Delivery object is instantiated with all default values except for one - its status. The Test Data Builder offers a an easy to use method to override these values. By using it, we can concentrate on the essence of this test - it becomes evident that only the status affects the test&#39;s outcome. Other properties, having no such impact, are not explicitly provided here. This results in a cleaner and more comprehensible test.</p><p>Furthermore, the way this object is set up closely resembles natural language. If we omit the brackets in the previous example, we obtain a clear statement: a delivery with status canceled. Comparing this to the initial approach with numerous constructor arguments, it becomes evident which is easier to read.</p><p>The last benefit and the essential reason why to use the discussed pattern is that it allows to construct objects for various classes. Typically, projects contain a subset of entities, data transfer objects (DTOs), domain classes, messaging classes, and more. While they serve different purposes, they often share common data fields/schemes. The logic for initializing each of them can be encapsulated in a method that takes all required data (whether default or overriden) stored in the Test Data Builder and invokes the class’s constructor to yield a new instance. This is the purpose of the previously mentioned entity() method in the examples - it prepares an entity class for use in the test.</p><h3>…which is not a magic at all…</h3><p>Let’s have our hands dirty and and dive into creating the Test Data Builder for delivery objects, as mentioned in the previous section.</p><p>The first step is to create a class with fields designed to hold the default values necessary for constructing the Delivery object:</p><pre>import java.util.UUID;<br><br>import static io.delivery.domain.DeliveryStatus.CREATED;<br>import static io.delivery.domain.FoodTestData.anFood;<br><br>class DeliveryTestData {<br><br>  private UUID id = UUID.randomUUID();<br>  private DeliveryStatus status = CREATED;<br>  private String address = &quot;Main Street 1, Naples&quot;;<br>  private List&lt;FoodTestData&gt; foods = List.of(aFood());<br>}</pre><p>It’s up to you how to name this class. Personally, I tend to use the suffix TestData, but other options like Builder work equally well. What&#39;s crucial is maintaining consistent naming conventions throughout the entire project.</p><p>It’s worth noting that the DeliveryTestData class contains a list of FoodTestData. This represents another builder for classes associated with food, and it is structured as follows:</p><pre>class FoodTestData {<br><br>  private String name = &quot;Pizza Margherita&quot;;<br>  private int amount = 1;<br>}</pre><p>The next step is to prohibit the creation of DeliveryTestData using the default constructor and, instead, enable its creation using the aDelivery() static factory method:</p><pre>class DeliveryTestData {<br><br>  private UUID id = UUID.randomUUID();<br>  private DeliveryStatus status = CREATED;<br>  private String address = &quot;Main Street 1, Naples&quot;;<br>  private List&lt;FoodTestData&gt; foods = List.of(aFood());  <br>  <br>  private DeliveryTestData() {};  <br>  <br>  public static DeliveryTestData aDelivery() {<br>    return new DeliveryTestData();<br>  }<br>}</pre><p>The same thing needs to be done in the second builder class — FoodTestData, but let&#39;s skip it here and all other further steps, to keep this post short.</p><p>Since all fields are private we need to allow to modify them. Instead of creating setters let’s create with... methods for each one of them:</p><pre>class DeliveryTestData {<br><br>  private UUID id = UUID.randomUUID();<br>  private DeliveryStatus status = CREATED;<br>  private String address = &quot;Main Street 1, Naples&quot;;<br>  private List&lt;FoodTestData&gt; foods = List.of(aFood());  <br>  <br>  private DeliveryTestData() {};  <br>  <br>  public static DeliveryTestData aDelivery() {<br>    return new DeliveryTestData();<br>  }<br><br>  public DeliveryTestData withId(UUID id) {<br>    this.id = id;<br>    return this;<br>  }<br><br>  public DeliveryTestData withStatus(DeliveryStatus status) {<br>    this.status = status;<br>    return this;<br>  }<br><br>  public DeliveryTestData withAddress(String address) {<br>    this.address = address;<br>    return this;<br>  }<br><br>  public DeliveryTestData withFoods(FoodTestData... foods) {<br>    this.foods = Arrays.asList(foods);<br>    return this;<br>  }<br><br>}</pre><p>Notice that each method returns the instance of modified class (“classic” setter method is always void). This little trick enables chaing with() methods, for example for the test we would like to override two default properties:</p><pre>class SomeTest {<br><br>  void test() {<br>      <br>    var delivery = aDelivery()<br>                     .withStatus(COMPLETED)<br>                     .withAddress(&quot;Main Road 2&quot;);<br>  }<br>}</pre><p>Next, crucial, part is to prepare methods that will be responsible for creating target objects. In my case I want to allow to create an entity and DTO (for controller) classes, but you can create more if needed.</p><pre>class DeliveryTestData {<br><br>  private UUID id = UUID.randomUUID();<br>  private DeliveryStatus status = CREATED;<br>  private String address = &quot;Main Street 1, Naples&quot;;<br>  private List&lt;FoodTestData&gt; foods = List.of(aFood());  <br>  <br>  private DeliveryTestData() {};  <br>  <br>  public static DeliveryTestData aDelivery() {<br>    return new DeliveryTestData();<br>  }<br><br>  public DeliveryEntity entity() {<br>    DeliveryEntity entity = new DeliveryEntity();<br>    entity.setId(id);<br>    entity.setStatus(status);<br>    entity.setAddress(address);<br>    entity.setFoods(<br>      foods.stream()<br>        .map(FoodTestData::entity)<br>        .toList()<br>    );<br><br>    return entity;<br>  }<br><br>  public DeliveryDTO dto() {<br>    return new DeliveryEntity(<br>        id,<br>        status,<br>        address,<br>        foods.stream()<br>            .map(FoodTestData::dto)<br>            .toList()<br>    );<br>  }<br><br>  //all with...(...) methods<br>}</pre><p>The final step is to enable the reading of values from private fields so that they can be used in test assertions. This can be achieved either by creating them manually or generating them using an IDE, as demonstrated here with the <a href="https://projectlombok.org/">Lombok</a> annotation:</p><pre>import lombok.Getter;<br><br>@Getter<br>class DeliveryTestData {<br><br>  //content of the class    <br>}</pre><h3>…and can be used in Integration tests!</h3><p>Test Data Builders are excellent for unit tests, but what about integration tests? In the setup phase of an integration test, we often need to insert data into the database. Can we also use these types of classes for that purpose? The answer is — absolutely!</p><p>Let’s assume we want to create an integration test for a custom repository method in a Spring Data repository. For instance, we have a method to retrieve the count of deliveries for a specific type of food:</p><pre>import org.springframework.data.jpa.repository.JpaRepository;<br>import org.springframework.data.jpa.repository.Query;<br>import org.springframework.data.repository.query.Param;<br><br><br>interface DeliveryRepository extends JpaRepository&lt;Delivery, UUID&gt; {<br><br><br>  @Query(nativeQuery = true, value = &quot;&quot;&quot;<br>  SELECT count(*) <br>  FROM delivery d <br>  LEFT JOIN delivery_food df ON d.id = df.delivery_id<br>  WHERE df.food_name = &#39;:foodName&#39;<br>  &quot;&quot;&quot;)<br>  long totalDeliveriesCountForFood(@Param(&quot;foodName&quot;) String foodName);<br>}</pre><p>A test can take this form, assuming that within the IntegrationTest class, there is code responsible for launching the database using Testcontainers (exemplary code can be found <a href="https://github.com/wkrzywiec/farm-to-table/blob/master/services/delivery/src/test/groovy/io/wkrzywiec/fooddelivery/delivery/IntegrationTest.groovy">here</a>). This allows us to skip the setup here:</p><pre>import org.springframework.beans.factory.annotation.Autowired;<br><br>class DeliveryRepositoryIntegrationTest extends IntegrationTest {<br><br>  @Autowired<br>  private DeliveryRepository deliveryRepository;<br>  private TestRepository testRepository;  <br>  <br>  @Before<br>  void init() {<br>      testRepository = new TestRepository(deliveryRepository);<br>  } <br><br>  void two_delivieres_are_returned_if_two_were_made() {<br>    //given<br>    String foodName = &quot;Pierogi&quot;;  <br>    DeliveryTestData delivery1 = aDelivery()<br>      .withFood(<br>        aFood()<br>          .withName(foodName)<br>      );  <br>    <br>    DeliveryTestData delivery2 = aDelivery()<br>      .withFood(<br>        aFood()<br>          .withName(foodName)<br>      );  <br>    <br>    testRepository.saveAll(delivery1, delivery2);  <br>    <br>    //when<br>    long result = deliveryRepository.totalDeliveriesCountForFood(foodName);  <br>    <br>    //then<br>    assertEquals(2, result);<br>  }<br>}</pre><p>The test above is very similar to the unit tests written earlier. All the setup phases are consolidated into one place, and it’s straightforward: two deliveries with the same food are created and then saved into a database. There is no need to create any SQL inserts to make this test work. So, what’s the key to this structure of an integration test? The TestRepository class.</p><p>As the name suggests, its purpose is to be used during tests to persist the Test Data Builder object in a database. There are several ways to implement it. Here’s one way it could be implemented for a Postgres database using Spring Data’s repository:</p><pre>import org.springframework.transaction.annotation;<br><br>public class TestRepository {<br><br>  private final DeliveryRepository deliveryRepository;  <br>  <br>  public TestRepository(DeliveryRepository deliveryRepository) {<br>    this.deliveryRepository = deliveryRepository;<br>  }<br><br>  @Transactional<br>  public List&lt;Delivery&gt; saveAll(DeliveryTestData... deliveries) {<br>    return Arrays.stream(deliveries)<br>      .map(this::save)<br>      .toList();<br>  }<br><br>  @Transactional<br>  public Delivery save(DeliveryTestData testData) {<br>    Delivery entity = testData.entity();<br>    return deliveryRepository.save(entity);<br>  }<br>}</pre><p>Here, you can see that the TestRepository is a straightforward wrapper for Spring Data&#39;s repository. It takes on this role because the object graph (comprising Delivery and a list of Food) is relatively simple. However, when dealing with more complex data structures or intricate ORM mappings (such as bidirectional many-to-one associations), it becomes beneficial to centralize entity preparation and persistence logic within the TestRepository class.</p><p>Of course, you’re not limited to the above solution. Instead of using Hibernate, you can implement it using JDBC (or Spring’s JdbcTemplate), jOOQ, or any other tool of your choice. So, should we use it everywhere?</p><p>Like any other tool, Test Data Builder should be employed where it fits best. Does it fit every project? Certainly not!</p><h3>When should you avoid using it?</h3><p>First and foremost, improved readability comes at the cost of higher maintenance. To use it, you need to create it first. If a data model is extensive (meaning many properties or elements in the object graph), it will take some time to set up Test Data Builder. Moreover, if the data model changes frequently, it will require more effort to keep up with these changes (though the same challenge applies to the classic approach).</p><p>Using it for a small project (e.g., serverless functions) might be overkill as well. In such cases, where a project has a small number of use cases, it may be quicker and easier to prepare specific data for each test case. The same holds true if a particular object graph is only used a couple of times. There’s no sense in creating a Test Data Builder and then using it just a few times. It’s better to set up those tests without it.</p><p>Lastly, not all projects follow the pattern of fetching data, modifying it, persisting it, and notifying an invoker of this action’s result. Some projects are less typical and may require a special approach.</p><p>Aside from these three cases, I encourage you to give Test Data Builder a try in your applications. With a little effort, you can reap the benefits described earlier.</p><p>It’s time to wrap up this post with a quick summary of why Test Data Builders are worth using:</p><ul><li>They provide a convenient and efficient way to prepare test data.</li><li>They enhance test readability by introducing notation close to natural language.</li><li>They conceal irrelevant parts of test data preparation, allowing you to focus only on the key aspects important for a particular test.</li><li>They offer a single location for initializing various data transfer objects used in tests.</li><li>Thanks to them, unit and integration tests can closely resemble each other.</li></ul><p>Of course, it’s important to remember that for certain projects, this pattern may not be the best choice. Especially for small or rapidly changing projects, the maintenance effort can be significant.</p><p>If you’re looking for more examples of Test Data Builders, feel free to check out my project on GitHub — <em>wkrzywiec/farm-to-table</em>.</p><p><a href="https://github.com/wkrzywiec/farm-to-table">GitHub - wkrzywiec/farm-to-table</a></p><h3>And if you need more sources</h3><p><a href="https://www.arhohuttunen.com/test-data-builders/">How to Create a Test Data Builder | Code With Arho</a></p><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/050_test-data/"><em>https://wkrzywiec.is-a.dev</em></a><em> on October 8, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3a8e0efd479" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java Series: Flatmap]]></title>
            <link>https://wkrzywiec.medium.com/java-series-flatmap-5756719fa83d?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/5756719fa83d</guid>
            <category><![CDATA[streaming]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[data-processing]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Sun, 12 Dec 2021 00:00:01 GMT</pubDate>
            <atom:updated>2023-01-01T17:19:48.820Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Java 8 was a great step forward toward modern programing language. One of the key features added in this release was Java streams. It provides lots of convenient operations for data processing. One of them is a </em><em>flatMap() used very widely to unwrap and merge multiple collections into one.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*0iq3dvhRr3bEiobp.jpg" /><figcaption><em>Image by </em><a href="https://unsplash.com/@ninjason"><em>Jason Leung</em></a><em> on </em><a href="https://unsplash.com/"><em>Unsplas</em></a><em>h</em></figcaption></figure><blockquote><em>This article is part of the </em>“Java Series”<em>, which covers useful Java functions from standard and popular libraries. More posts on that can be found on </em><a href="https://wkrzywiec.medium.com/"><em>Medium</em></a><em> or on </em><a href="https://wkrzywiec.is-a.dev/tags/java-series?utm_source=medium"><em>my home page here</em></a><em>.</em></blockquote><h3>Problem statement</h3><p>Many times when we work with Java code we end up with a following Plain Old Java Object (POJO):</p><pre>public record Parent(List&lt;Child&gt; childs) {}</pre><p>They can represent database entities or data transfer objects (DTOs). In general, they’re used to structure data. Let’s say that we got the list of Parent objects, but we want to operate on a list of all Child that are part of the Parent. How we could extract Child objects from all Parent objects and combine them into a single list? The naive approach would be to use a loop:</p><pre>List&lt;Child&gt; children = new ArrayList&lt;&gt;();<br><br>for (Parent parent: parents) {<br>    List&lt;Child&gt; bs = parent.childs();<br>    children.addAll(bs);<br>}</pre><p>But it doesn’t look nice and clean. Instead, we could use the Java stream:</p><pre>List&lt;Child&gt; children = new ArrayList&lt;&gt;();<br><br>parents.stream()<br>    .map(parent -&gt; parent.Childs())<br>    .forEach(list -&gt; children.addAll(list));</pre><p>But it has a drawback too. Let’s say that once we get a list of all Child objects we would like to modify them, aggregate, or do calculations on them. Ideally it would be good to perform those action in the same stream. Unfortunately, this is not the case, the forEach() method in the above example is ending the stream processing, which makes it impossible to process Child records in the same stream.</p><h3>Solution</h3><p>Luckily Java creators foresaw this problem and introduced a flatMap() function that is part of a java.util.stream.Stream class.</p><p>The idea is pretty straightforward. It does two things with every element of a stream:</p><ul><li>maps — transforms one element from a stream into a new stream, so as a result we would get a stream of streams,</li><li>flattens — results of a previous operation are merged into one stream.</li></ul><p>To visualize it consider the following situation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*XkNTejviymsYJONF.png" /></figure><p>Let’s say that we have a stream of Author objects, that has a method called books() which returns a list of Book objects. And now let&#39;s say that we would like to have access to all books written by all authors to make further operations on them. If we would use the map() function within which we would call the books() method we would get a stream of lists of Book objects. This is not what we want to have.</p><p>What we would like to have is a stream of Book objects, not a stream of their lists. How we can overcome it? Using flatmap() instead:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*339GeVRWJiBdZ-_x.png" /></figure><p>As previously we need to invoke the books() method of the Author class to get a list of Books. The only difference is that input needs to be converted into multiple values represented by a Java stream. This is the requirement of the flatMap() method. Whatever operations we do within it needs to return a Stream&lt;T&gt; object.</p><p>The same situation can be reflected with a code:</p><pre>List&lt;List&lt;Book&gt;&gt; listsOfListOfBooks = authors.stream()<br>    .map(Author::books)<br>    .toList();<br><br>List&lt;Book&gt; listOfBooks = authors.stream()<br>    .flatMap(author -&gt; author.books().stream())<br>    .toList();</pre><p>To translate a list of Book objects the standard Collection.stream() method was used.</p><h3>When to use it?</h3><h4>Unwrap &amp; operate</h4><p>The most common case when the flatMap() method might be handy is when one of the stream operations produces a collection of objects and we would like to make further actions on each one of them.</p><p>To visualize it let’s go back to the previous example with Book and Author records. Here are their definitions:</p><pre>public record Book(String title) {}<br>public record Author(String name, List&lt;Book&gt; books) {}</pre><p>Now let’s say that we want to create a method that takes a list of Author objects as input and produces the list of all book titles that these authors wrote:</p><pre>List&lt;String&gt; getAllBookTitles(List&lt;Author&gt; authors) {<br>return authors.stream()<br>    .flatMap(author -&gt; author.books().stream())<br>    .map(Book::title)<br>    .toList();<br>}</pre><p>After converting a list of Author objects into a stream the flatMap() operation is used first. The books() method is invoked to get their list and then it&#39;s changed into the stream. The flatMap() is then merging all resulting streams into one so next the title() can be called to get a String representation of a book title. Finally, the results of each element in a stream are collected into the list.</p><p>The above method can be written even better. We can split invoking books() and stream() methods into two operations - map() and flatMap() respectively - to get a nice looking code:</p><pre>List&lt;String&gt; getAllBookTitles(List&lt;Author&gt; authors) {<br>return authors.stream()<br>    .map(Author::books)<br>    .flatMap(List::stream)<br>    .map(Book::title)<br>    .toList();<br>}</pre><h4>Merge lists</h4><p>Another case when flatMap() can be very useful is when we would like to combine two or more lists (or any other java.util.Collection).</p><pre>List&lt;String&gt; mergeLists(List&lt;String&gt; left, List&lt;String&gt; right) {<br>return Stream.of(left, right)<br>    .flatMap(List::stream)<br>    .toList();<br>}</pre><p>A big plus for this approach is that after flatMap() we don&#39;t need to close the stream immediately. Instead, we can apply other operations on every object, like filtering, mapping, aggregating, etc. Which is cleaner and more efficient.</p><h4>Get value from nested Optional</h4><p>Apart from Java streams flatMap() method can be invoked on an Optional object. It&#39;s used to unwrap an Optional that is nested inside another Optional.</p><p>Let’s say that we’ve got the following record:</p><pre>public record Address(String street, String buildingNo, Optional&lt;String&gt; apartmentNo) {}</pre><p>Now suppose we would have an Optional&lt;Address&gt; and would like to extract a value of an apartmentNo. The code without flatMap() would look like this:</p><pre>String extractApartmentNo(Optional&lt;Address&gt; address) {<br>    if (address.isEmpty()) {<br>        return &quot;&quot;;<br>    }<br><br>    return address.get().apartmentNo().orElse(&quot;&quot;);<br>}</pre><p>The first step is to unwrap the value from the Optional, which might be empty. Only after checking it, we can proceed with unwrapping (and handling empty values) an apartment address.</p><p>This approach is ok, but can be done better with flatMap():</p><pre>String extractApartmentNo(Optional&lt;Address&gt; address) {<br>return address<br>        .flatMap(Address::apartmentNo)<br>        .orElse(&quot;&quot;);<br>}</pre><p>This approach a way nicer than the previous one. Both Optionals — parent and child — are validated whether they hold a null value in a single expression.</p><p>Introducing streams into Java made Data processing easier. It brings us a lot of handy operations. flatMap() is one of them which gives us the possibility to merge multiple streams into one or flatten nested streams into one. It&#39;s a very common pattern and is used many times in real projects.</p><p>Code and tests from this post can be found in my repository:</p><p><a href="https://github.com/wkrzywiec/java-series">GitHub - wkrzywiec/java-series: Repo with various Java examples</a></p><h3>References</h3><ul><li><a href="https://www.oracle.com/java/technologies/architect-streams-pt2.html">Part 2: Processing Data with Java SE 8 Streams</a></li><li><a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#flatMap-java.util.function.Function-">Stream (Java Platform SE 8 )</a></li><li><a href="https://www.oracle.com/technical-resources/articles/java/java8-optional.html">Tired of Null Pointer Exceptions? Consider Using Java SE 8&#39;s Optional!</a></li></ul><p><em>Originally published at </em><a href="https://wkrzywiec.is-a.dev/posts/049_java-series-flatmap?utm_source=medium"><em>https://wkrzywiec.is-a.dev</em></a><em> on December 12, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5756719fa83d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to set up monitoring tools for Java application]]></title>
            <link>https://wkrzywiec.medium.com/how-to-set-up-monitoring-tools-for-java-application-322d14c191e4?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/322d14c191e4</guid>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[jvm]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Thu, 30 Sep 2021 04:00:10 GMT</pubDate>
            <atom:updated>2021-10-01T06:29:12.838Z</atom:updated>
            <content:encoded><![CDATA[<p><em>In this blog post I’ll show how to set up two monitoring tools that are widely used for Java applications — Java Flight Recorder with JDK Mission Control and Prometheus with Grafana.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*31571yeSYkiHH8hf" /><figcaption>Photo by <a href="https://unsplash.com/@towfiqu999999?utm_source=medium&amp;utm_medium=referral">Towfiqu barbhuiya</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When your application reaches a state when it’s production ready it’s a good time to celebrate. Soon users will start using it, which is great, but it creates a new type of problem. How to monitor how an application behaves under the load of users traffic? How to validate if everything goes smoothly and users are not experiencing any kind of slowness or errors? The answer to these questions would be to use a performance monitoring tools.</p><p>With this blog post I’ll show how to set up two opensource monitoring tools available for JVM — <em>Java Flight Recorder</em> with <em>JDK Mission Control</em> and <em>Prometheus</em> with <em>Grafana</em>. It’s a first article out of two. In this one I’ll focus on installing both tools and in the second I’ll explain how read the indicators that both of these tools are providing.</p><h3>Project preparation</h3><p>The entire code base that will be mentioned thru the article is available on GitHub — <a href="https://github.com/wkrzywiec/monitoring-sandbox">monitoring-sandbox</a>. It contains a source code of a Java application and all configuration files for both monitoring tools.</p><p>The only thing that is required is to have <a href="https://www.docker.com/">Docker</a> installed. Using simple Docker Compose command (docker-compose up -d) we can start all necessary pieces (monitored application with <em>JFR</em> enabled, <em>Prometheus</em> and <em>Grafana</em>) so you don&#39;t need to follow all my steps. They&#39;re just for explanation what&#39;s going on.</p><h3>Setting up Java Flight Recorder with JDK Mission Control</h3><p>Let’s now move to more practical part of the post and have a look on <strong>JDK Flight Recorder (JFR)</strong> and <strong>JDK Mission Control (JMC)</strong>.</p><p>As a name might suggest for a first one it has something in common with black box flight recorder used in airplanes. Similarly to the ones used in aircrafts their primary function is to record and save everything that is happening inside a plane, so in case of an accident specialists could rewind all the steps back to find out what was the cause to prevent from happening it again. In Java <em>JFR</em> plays similar role. It records events that occur inside JVM and application. It collects them and store e.g. in a file. Many of events are already built-in, which enables us to see what’s happening inside our application, e.g. we can see how Garbage Collector is working, how Threads are used etc., but it’s also possible to <a href="https://bell-sw.com/announcements/2021/01/29/JDK-Flight-Recorder-The-Programmatic-Way/">define custom events</a> inside an application. It could be used for monitoring business processes and performance or whatever we would like.</p><p>As already mentioned an output from <em>JFR</em> will be a recording, usually a file. Now how to open it? It could be achieved by using jfr CLI which is bundled with JDK since version 11 (for open source version of Java distributions, for commercial ones it was from Java 7). It allows to list all events by the type and print them in the consol. But it&#39;s not convenient. Luckily there is a tool which was already mentioned - <strong>JDK Mission Control (JMC)</strong>.</p><p><em>JMC</em> is a desktop tool that can read <em>JFR</em> recordings and visualize it in a very nice way. Similarly to <em>JFR</em> it was bundled with JDK, but now it’s distributed separately and can be downloaded from <a href="https://www.oracle.com/java/technologies/jdk-mission-control.html">the official website</a>.</p><p>Having this introduction let’s play around with <em>JFR</em>. For demonstration I’ve prepared a small Java project that is available on GitHub — <a href="https://github.com/wkrzywiec/monitoring-sandbox">monitoring-sandbox</a>. For convenience we will be running an app using Docker. Therefore here is a <strong>Dockerfile</strong> located in a <em>/app</em> folder:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/94182a82ba60b5db5991de38a5e31e74/href">https://medium.com/media/94182a82ba60b5db5991de38a5e31e74/href</a></iframe><p>It’s a very simple configuration. The application is running on a recently released OpenJdk 17 from Oracle. This is very important information because depending on the distribution of JDK we can have different results as internals of each distribution might be a little different.</p><p>Another thing that is worth to mention is a line with RUN mkdir -p /recordings. With it we&#39;re creating a folder inside Docker container in which we will next store <em>JFR</em> recordings.</p><p>We could run an app using docker run command, but let&#39;s have another approach and use Docker Compose, which will also be used in the next part when we&#39;ll be setting up Grafana. Here is the <strong>docker-compose.yaml</strong> from the project:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/66328ec20ee768c6c7efe798ee931872/href">https://medium.com/media/66328ec20ee768c6c7efe798ee931872/href</a></iframe><p>A service is called jvm and its Docker image is build using a <em>Dockerfile</em> from <em>/app</em> directory. With volumes we&#39;re mapping a project&#39;s <em>recording</em> directory with a directory with the same name inside a Docker container. It&#39;s necessary to add it, because <em>JFR</em> recordings will be saved there and in order to use them in <em>JDK Mission Control</em> we need extract them first from a running container. Finally there is ports mapping provided.</p><p>Having all of that we can move on to the main course — starting an application along with <em>Java Flight Recorder</em>.</p><p>There are several ways to start recording. One of them is to enable it using the -XX:StartFlightRecording argument of the java command during application startup. This is why there is an entrypoint provided in the <em>docker-compose.yaml</em> file. Below there is a command that is used to bootstrap an application:</p><pre>&gt; <strong>java -XX:StartFlightRecording:filename=/recordings/recording.jfr,dumponexit=true -jar app.jar</strong></pre><p>To tune <em>JFR</em> recording we can add some flags. In above example two of them were introduced:</p><ul><li>filename - specifies location and name of a recording, i.e. /recordings/recording.jfr inside of a container,</li><li>dumponexit - if in an application crashes or just stop we can specify if a recording will be dumped, or in other words if <em>JFR</em> should save a recording after stopping a program. In our case a value is true which means it will be persisted.</li></ul><p>More of available options can be found <a href="https://docs.oracle.com/cd/E15289_01/JRCLR/optionxx.htm#JRCLR275">here</a> and <a href="https://docs.oracle.com/cd/E15289_01/JRCLR/optionxx.htm#BABIECII">here</a>.</p><p>Let’s start an application and see what will happen. To do that just run following command:</p><pre>&gt; <strong>docker-compose up -d jvm</strong></pre><p>Now inside /recordings folder there should be a <em>recording.jfr</em> file. If we want now to use <em>JDK Mission Control</em> to inspect recording file we would receive an error. The reason is that first we need to end recording, only then a file can be read. Therefore we need to stop a program:</p><pre>&gt; <strong>docker-compose down</strong></pre><p>After that we are able to use the <em>recording.jfr</em> file in <em>JDK Mission Control</em>.</p><p>But is it possible to get access to recording other way than “crashing” an application? Sure it is!</p><p>First of all we need to get inside the running container. We can achieve it by opening an interactive shell for a container with a name jvm-app:</p><pre>&gt; <strong>docker exec -it jvm-app sh</strong></pre><pre>sh-4.4<em>#</em></pre><p>As a result we’re in the running container. If for example we run the ls command we gets an information what&#39;s inside a container (starting from now on all commands will be executed inside of the container and it&#39;s manifested by the sh-4.4#):</p><pre>&gt; <strong>docker exec -it jvm-app sh</strong></pre><pre>sh-4.4<em># </em><strong><em>ls</em></strong><br>app.jar  bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  recordings  root  run  sbin  srv  sys  tmp  usr  var</pre><p>To control <em>JFR</em> we use a jcmd CLI which is bundled with JDK. To check what other tools are provided with JDK run the command:</p><pre>sh-4.4<em># </em><strong><em>ls $JAVA_HOME/bin</em></strong></pre><pre>jar        java   javadoc  jcmd      jdb        jdeps  jhsdb   jinfo  jmap  jpackage  jrunscript  jstack  jstatd   rmiregistry<br>jarsigner  javac  javap    jconsole  jdeprscan  jfr    jimage  jlink  jmod  jps       jshell      jstat   keytool  serialver</pre><p>But for now let’s focus on jcmd. By typing only the jcmd we will get a list of Java processes that are happening right now inside of the container:</p><pre>sh-4.4<em># </em><strong><em>jcmd</em></strong></pre><pre>1 app.jar<br>183 jdk.jcmd/sun.tools.jcmd.JCmd</pre><p>Above we can see that there is an app.jar process with a pid (process id) which equals 1. To check if there is an active recording for app.jar type following command:</p><pre>sh-4.4<em># </em><strong><em>jcmd 1 JFR.check</em></strong><br>1:<br>Recording 1: name=1 maxsize=250.0MB (running)</pre><p>Recording is active, great! Now let’s dump what has been collected so far into a separate file located in the <strong>recordings</strong> folder:</p><pre>sh-4.4<em># </em><strong><em>jcmd 1 JFR.dump filename=/recordings/short-recording.jfr</em></strong><br>1:<br>Dumped recording, 3.2 MB written to:</pre><pre>/recordings/short-recording.jfr</pre><p>In above command first we specify apid, which is 1 as we already checked, and then a JFR.dump command that tells to write a recording to a file located in specific directory (filename=/recordings/short-recording.jfr).</p><p>Let’s now check a recording using jfr CLI which, as well as jcmd, is provided in JDK.</p><pre>sh-4.4<em># </em><strong><em>jfr summary /recordings/short-recording.jfr</em></strong></pre><pre> Version: 2.1<br> Chunks: 1<br> Start: 2021-09-26 17:26:22 (UTC)<br> Duration: 1081 s</pre><pre> Event Type                                   Count  Size (bytes)<br>==================================================================<br> jdk.NativeMethodSample                       53054        635109<br> jdk.JavaMonitorWait                          51660       1393022<br> jdk.ThreadPark                                2489         96828<br> jdk.GCPhaseParallel                           2195         53536<br> jdk.CheckPoint                                1184        833801<br> jdk.CPULoad                                   1079         21547<br> jdk.JavaThreadStatistics                      1079         12915<br> jdk.ClassLoadingStatistics                    1079         11836<br> jdk.CompilerStatistics                        1079         31258<br> jdk.ExceptionStatistics                       1079         15073<br> jdk.ModuleExport                               946         10256<br> jdk.ObjectAllocationSample                     780         11772<br> jdk.BooleanFlag                                533         15762<br> jdk.ActiveSetting                              355         10945<br> jdk.ThreadCPULoad                              314          5311</pre><p>We can print all events that are stored in recording, but be aware that if your recording was very long you’ll get a huge amount of events:</p><pre>sh-4.4<em># </em><strong><em>jfr print /recordings/short-recording.jfr</em></strong></pre><pre>jdk.ActiveSetting {<br>  startTime = 17:26:22.702<br>  id = 1185<br>  name = &quot;threshold&quot;<br>  value = &quot;0 ns&quot;<br>  eventThread = &quot;main&quot; (javaThreadId = 1)<br>}</pre><pre>jdk.ActiveSetting {<br>  startTime = 17:26:22.702<br>  duration = 0.732 ms<br>  id = 132<br>  name = &quot;period&quot;<br>  value = &quot;beginChunk&quot;<br>  eventThread = &quot;main&quot; (javaThreadId = 1)<br>}</pre><pre>jdk.ActiveSetting {<br>  startTime = 17:26:22.702<br>  duration = 0.732 ms<br>  id = 132<br>  name = &quot;enabled&quot;<br>  value = &quot;true&quot;<br>  eventThread = &quot;main&quot; (javaThreadId = 1)<br>}</pre><p>It might be quite overwhelming, therefore we can filter them out using --categories option:</p><pre>sh-4.4<em># </em><strong><em>jfr print --categories &#39;GC&#39; /recordings/short-recording.jfr</em></strong></pre><pre>jdk.GCPhasePause {<br>  startTime = 17:32:37.583<br>  duration = 3.55 ms<br>  gcId = 10<br>  name = &quot;GC Pause&quot;<br>  eventThread = &quot;VM Thread&quot; (osThreadId = 14)<br>}</pre><pre>jdk.GarbageCollection {<br>  startTime = 17:32:37.583<br>  duration = 3.55 ms<br>  gcId = 10<br>  name = &quot;G1New&quot;<br>  cause = &quot;Metadata GC Threshold&quot;<br>  sumOfPauses = 3.55 ms<br>  longestPause = 3.55 ms<br>}</pre><pre>jdk.YoungGarbageCollection {<br>  startTime = 17:32:37.583<br>  duration = 3.55 ms<br>  gcId = 10<br>  tenuringThreshold = 15<br>}</pre><pre>jdk.G1GarbageCollection {<br>  startTime = 17:32:37.583<br>  duration = 3.55 ms<br>  gcId = 10<br>  type = &quot;Concurrent Start&quot;<br>}</pre><p>Even if we can filter those events and as they are quite easy to read still they’re pretty hard to analyze. We need to have a bigger picture. So this is why we need to use a different tool — <em>JDK Mission Control</em>, which will make for us some visualizations.</p><p>First exit from a running container, so just type exit. Now let&#39;s open <em>JMC</em> assuming that we have already installed it from <a href="https://www.oracle.com/java/technologies/jdk-mission-control.html">the official website</a>. The procedure will depends on the OS system and it&#39;s described in <a href="https://www.oracle.com/java/technologies/javase/jmc8-install.html">instructions</a>.</p><p>Once it has started we need to load recording file. Therefore select <strong>File</strong> &gt; <strong>Open file…</strong> and find a recording file, which can be either a short recording or the entire one that’s created when application shuts down (remember both of them are located in <em>recordings</em> folder). After that you should get similar screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gwsRkFCmM9YkG0jpb3Q54g.png" /></figure><p>On the left there are couple categories into which we can dive in. After clicking on one of them we can see more details, e.g. by clicking on <strong>Memory</strong> we can get an insight of application memory allocated and how long Garbage collection took:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PfIIUW4KqLljTt6yTseA8Q.png" /></figure><p>We can see even more details about Garbage Collection by clicking on the <strong>Garbage Collection</strong> item:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NXdSxTdgW0obdi95gtUoPg.png" /></figure><p>Another good example of information that we can get from <em>JMC</em> is total CPU usage (<strong>Processes</strong> item):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9jJyStHjwoY045VnnwHmGQ.png" /></figure><p>But there a way more information that we can read from it, which I would encourage you to explore on your own (or check my next article on that topic 😉). For now this was everything that I would like to show and let’s move on to the next section.</p><h3>Setting up JVM dashboards in Grafana</h3><p>Previously discussed <em>JFR</em> is an amazing tool to get very in depth information about working JVM. But the tricky part is how to set it up so the information will be collected and stored in continuous way, without manually dumping data every time we would like to make an analysis. The solution to such inconvenience would be to make a use of something that’s called <a href="https://blogs.oracle.com/javamagazine/post/java-flight-recorder-and-jfr-event-streaming-in-java-14">JFR Event Streaming</a>, but probably it was introduced in Java 14 it’s still in very early stages and not many tools were built on top of that. Possible reason is that vast majority of an industry still sticks with the latest LTS version of Java, which is 11. With a recent release of Java 17, which is an LTS version, it will change soon as more and more companies will be migrating to it.</p><p>Until then there are other tools which might come with help. There are dozens of them some open-sourced, some commercial. Today I’ll show you how to use a very popular ones — <strong>Prometheus</strong> and <strong>Grafana</strong>.</p><p>Here are all the pieces of a project that will be built:</p><ul><li><a href="https://micrometer.io/">Micrometer</a> — allows to expose metrics from JVM application,</li><li><a href="https://prometheus.io/">Prometheus</a> — scrapes and stores metrics from applications,</li><li><a href="https://grafana.com/">Grafana</a> — visualize stored metrics.</li></ul><p>Small remark, before we start. If you don’t want to setup everything on your own, you can jump straight forward to the next part. All code is available in my <em>GitHub</em> repository — <a href="https://github.com/wkrzywiec/monitoring-sandbox">https://github.com/wkrzywiec/monitoring-sandbox</a>.</p><p>First step is to expose metrics by the application itself. They will be than periodically collected and stored by <em>Prometheus</em>. Exposing metrics is very easy in <em>Spring Boot</em> application. The only thing that is needed is to add micrometer-registry-prometheus dependency (e.g. to Maven&#39;s pom.xml file):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/549d67799ff320949a83f463fb213b54/href">https://medium.com/media/549d67799ff320949a83f463fb213b54/href</a></iframe><p>Another thing that should be added, but it’s not a must, is to include application name into exposed metrics which can be done by adding management.metrics.tags.application property to <em>application.properties</em> or <em>application.yaml</em> file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/28e5616baba16e473d9312bb3c78ff2a/href">https://medium.com/media/28e5616baba16e473d9312bb3c78ff2a/href</a></iframe><p>After running an application and entering its <strong>/actuator/prometheus</strong> endpoint something like this should appear:</p><pre># HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the (young) heap memory pool after one GC to before the next<br># TYPE jvm_gc_memory_allocated_bytes_total counter<br>jvm_gc_memory_allocated_bytes_total{application=&quot;jvm-app&quot;,} 6.291456E7<br># HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management<br># TYPE jvm_memory_max_bytes gauge<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#39;non-nmethods&#39;&quot;,} 5840896.0<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;nonheap&quot;,id=&quot;Metaspace&quot;,} -1.0<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;heap&quot;,id=&quot;G1 Old Gen&quot;,} 3.31350016E9<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#39;profiled nmethods&#39;&quot;,} 1.22908672E8<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;heap&quot;,id=&quot;G1 Survivor Space&quot;,} -1.0<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;nonheap&quot;,id=&quot;Compressed Class Space&quot;,} 1.073741824E9<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;heap&quot;,id=&quot;G1 Eden Space&quot;,} -1.0<br>jvm_memory_max_bytes{application=&quot;jvm-app&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#39;non-profiled nmethods&#39;&quot;,} 1.22908672E8</pre><p>Great! Metrics are now exposed by the application, so we can move on to building monitoring infrastructure with Docker.</p><p>Because both <em>Prometheus</em> and <em>Grafana</em> are standalone application we need first to run them. For convenience I’m using Docker Compose, therefore in the <strong>docker-compose.yaml</strong> file I’ve defined 3 services:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b5625222996d3bf4f94e05961e513463/href">https://medium.com/media/b5625222996d3bf4f94e05961e513463/href</a></iframe><p>First service, jvm, is a Spring Boot application which will be monitored and it&#39;s the same appliacation that was investigated before.</p><p>Second service, prometheus, will be used to collect and store metrics. It&#39;s based on an official Docker image prom/prometheus, it&#39;s exposing 9090 port and map it to a local 2000 port. Because <em>Prometheus</em> persists metrics data over time, we need to create a <em>volume</em> so it will be saved between container startups on local disk.</p><p>But that’s not enough. We need to tell <em>Prometheus</em> from where and how to collect data. Luckily, there is an easy way. We can provide a prometheus.yml configuration file and insert it into specific folder in a running container.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c36a9f736e67699cf2e94d1208b8368a/href">https://medium.com/media/c36a9f736e67699cf2e94d1208b8368a/href</a></iframe><p>For one application only one scrape job is defined. It contains information about the address of scraped application and how often <em>Prometheus</em> needs to call it.</p><p>The last service, grafana, is used to visualize downloaded data. Like <em>Prometheus</em> it&#39;s based on an official Docker image and is exposing 3000 port. Beside that there are couple things added more for configuration purposes. With environment variables we&#39;re providing address to <em>Prometheus</em> service, data source of metrics. Next, in volumes section we&#39;re mapping couple of local folders into the <em>Grafana</em>&#39;s container.</p><p>First file is <em>config.ini</em>, with simple configs (more about it can be found in <a href="https://grafana.com/docs/grafana/latest/administration/configuration/">the official docs</a>):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9bb8ab926a15144d6152ca335961f6e3/href">https://medium.com/media/9bb8ab926a15144d6152ca335961f6e3/href</a></iframe><p>Next is a <em>provisioning</em> folder with two subfolders. First one, <em>datasources</em>, contains <em>datasources.yml</em> file, which helps to add <em>Prometheus</em> datasource:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7496995df6609b6ac962ac95c988754f/href">https://medium.com/media/7496995df6609b6ac962ac95c988754f/href</a></iframe><p>In the second folder, <em>dashboards</em>, there is also one YAML file <em>dashboards.yml</em>, in which we configure from where dashboards definitions will be loaded:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e351785b0e9c028ae6d0a613f9d39f20/href">https://medium.com/media/e351785b0e9c028ae6d0a613f9d39f20/href</a></iframe><p>On the same level as <em>provisioning</em> folder create another one called <em>dashboards</em>. For now it will be empty, but soon we will add here a JVM dashboard definition so it will be persisted.</p><p>All configurations files I’ve included in a common <em>infra</em> folder, which structure is as following (or you can just check it in GitHub repository):</p><pre>.<br>├───grafana<br>│   ├───dashboards<br>│   └───provisioning<br>│       ├───dashboards<br>│       └───datasources<br>└───prometheus</pre><p>Having all of that, we can now run the entire stack using <em>Docker Compose</em>.</p><pre>&gt; <strong>docker compose up -d</strong></pre><pre>[+] Building 33.6s (14/14) FINISHED<br>...                                                                                         0.0s </pre><pre>Use &#39;docker scan&#39; to run Snyk tests against images to find vulnerabilities and learn how to fix them<br>[+] Running 4/4<br> - Network &quot;monitoring-sandbox_default&quot;  Created                                                                                                     0.0s <br> - Container prometheus                  Started                                                                                                     1.2s<br> - Container jvm-app                     Started                                                                                                     1.2s <br> - Container grafana                     Started                                                                                                     3.3s</pre><p>Great, all three containers are now running. Let’s go now to Grafana’s UI, therefore in a web browser enter http://localhost:3000 address so the login page will show up. Then provide a username and password which is <strong>admin</strong> in both cases. After successful login a main page should be visible (you will be asked to change the password, but you can go ahead and skip it).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YrjXTAmfl0cEFgKBmmjHlA.png" /></figure><p>In a meantime <em>Prometheus</em> is already collecting metrics from the <em>jvm-app</em>, but we’re not yet able to visualize it in <em>Grafana</em>. To fix it we need to have a dashboard. We could create our own one, but there is already a great one available to be imported, which was prepared by the community.</p><p>To add it, from left panel select <strong>Create</strong> (plus sign) and than <strong>Import</strong>. A new page will appear where we need to provide an ID of dashboard which we’re gonna import. I want to import a very popular one — <a href="https://grafana.com/grafana/dashboards/4701">JVM (Micrometer)</a> with an id = 4701.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vNCU9lIVWyVXLe5yAymllA.png" /></figure><p>After providing it in an input field and clicking <strong>Load</strong> button a summary page will be visible. We can decide what will be the name of imported dashboard, its location, identifier and the most important, don’t forget to select it, a data source from where data will be pulled for visualizations. In our case it should be available only one source — <strong>prometheus</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F3max2ICkJdOnM30zyad1A.png" /></figure><p>After clicking <strong>Import</strong> button a dashboard will be created.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U205ecpfwFebtytFgoMCSQ.png" /></figure><p>The last thing that we need to do here is to export newly created dashboard to JSON file and place it to the empty <em>dashboards</em> folder, mentioned couple lines before. To do that click on the share icon (left, near the star icon) so the pop up window will appear. Next, go to <strong>Export</strong> tab and click <strong>Save to file</strong> so a file will be downloaded.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FIoQPZmhzXmmfLfNNpCdYw.png" /></figure><p>Next copy-paste it to the empty <em>dashboards</em> folder, stop containers (using docker compose down command) and restart them. After that a dashboard will be persisted.</p><p>That would be it! The dashboard is set, you can check it, see what indicators it’s presenting, like memory usage, CPU usage or others. Definitely there are here way less information than we could read from the <em>JDK Mission Control</em>, but they’re one of the most important one.</p><h3>Conclusion</h3><p>With this article I’ve showed how to set up two popular monitoring tools that can be used in your production system. They can offer lots of in-depth information what’s happening with your application and JVM. It can be especially helpful when something mysterious is starting to happen. Information about memory allocation, Garbage collection, CPU usage etc. can than help you find a root cause. But how to make sense from all these metrics? This is a story for another article, which will be published soon.</p><p>Until then, see you soon!</p><h3>References</h3><ul><li><a href="https://www.infoq.com/presentations/monitoring-jdk-jfr/">Continuous Monitoring with JDK Flight Recorder (JFR)</a></li><li><a href="https://bell-sw.com/announcements/2020/06/24/Java-Flight-Recorder-a-gem-hidden-in-OpenJDK/">JDK Flight Recorder - a gem hidden in OpenJDK</a></li><li><a href="https://github.com/petrbouda/docker-java-flight-recorder">GitHub - petrbouda/docker-java-flight-recorder</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=322d14c191e4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating quality checks for Kubernetes YAMLs]]></title>
            <link>https://wkrzywiec.medium.com/automating-quality-checks-for-kubernetes-yamls-ceb082800722?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/ceb082800722</guid>
            <category><![CDATA[datree]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[ci-cd-pipeline]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Tue, 21 Sep 2021 18:36:49 GMT</pubDate>
            <atom:updated>2021-09-21T18:36:49.902Z</atom:updated>
            <content:encoded><![CDATA[<p><em>If you have ever wondered how to make sure that your YAML Kubernetes objects are defined correctly and are following industry best practices, this blog post is for you. In a few paragraphs, I’ll show you how to create a GitHub Actions workflow that will first analyze K8s object definitions using Datree, then deploy it on a cluster and run some tests.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MJUD8amqN3pgLNSo" /><figcaption>Photo by <a href="https://unsplash.com/@tegethoff?utm_source=medium&amp;utm_medium=referral">Mark Tegethoff</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>It doesn’t matter if you’ve just started your journey with Kubernetes or you’re already an expert in it; writing object definitions is not an easy task. You can very easily make a mistake that could be very costly, if deployed to a production environment. And if you’re just starting to learn Kubernetes, you might need help understanding which metadata and specifications should be provided, but, by design, are not mandatory.</p><p>The ideal way to navigate these challenges would be to have an experienced colleague perform a review of your code changes. But sometimes you don’t have such a person around, or it’s very hard to get feedback from them. And even if you do have an amazing person to help you, it still won’t prevent you from making mistakes from time to time.</p><p>Instead, I would like to show you another approach: create a simple, automated quality check of your K8s object definitions using <a href="https://www.datree.io/?utm_source=Wojtek&amp;utm_medium=devto+article&amp;utm_campaign=Automating+quality+check+for+Kubernetes+YAMLs&amp;utm_id=influencer">Datree.io</a>, <a href="https://cloud.google.com/kubernetes-engine">Google Kubernetes Engine</a> and <a href="https://github.com/features/actions">Github Actions</a>.</p><p>We’ll build a GitHub Actions workflow which will be triggered after each change made on the <em>master</em> branch. The workflow will have two stages:</p><ul><li><em>Datree</em> analysis of missing configurations, and quality check,</li><li>Deployment and testing an example application in a real cluster (<em>GKE</em>).</li></ul><p>So roll up your sleeves and let’s automate it!</p><h4>Workflow structure</h4><p>First of all you need a GitHub repository to hold our YAML files. I’m using my old project — <a href="https://github.com/wkrzywiec/k8s-helm-helmfile">k8s-helm-helmfile</a>. This repository has three folders, each containing a different approach to deploy applications into Kubernetes clusters. You can read more about those approaches in my previous blog posts about <a href="https://wkrzywiec.medium.com/deployment-of-multiple-apps-on-kubernetes-cluster-walkthrough-e05d37ed63d1">vanilla K8s</a>, <a href="https://wkrzywiec.medium.com/how-to-deploy-application-on-kubernetes-with-helm-39f545ad33b8">Helm</a> and <a href="https://medium.com/swlh/how-to-declaratively-run-helm-charts-using-helmfile-ac78572e6088">helmfile</a> deployments.</p><p>To keep this blog post short, I’ll show how to create a workflow that will use Helm to deploy an application, but you can easily do this with other approaches, such as those mentioned previously.</p><p>The first step is to create a workflow definition file. In the root folder of a repository create a new directory <em>./github/workflows</em> inside of which there will be a master.yaml file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/05f8f4254b40374db60b03831cd4afef/href">https://medium.com/media/05f8f4254b40374db60b03831cd4afef/href</a></iframe><p>It contains following specifications:</p><ul><li>name - name of our workflow</li><li>on - a condition based on which a workflow will be triggered. A workflow will only be started when changes are committed on the master branch.</li></ul><p>With that, you can move on to the best part — defining jobs.</p><h3>Datree analysis</h3><p>In this part you will use a free tool called <em>Datree</em>, which analyzes K8s definitions and will stop the workflow if it finds any problems. It’s very important to have a safety net like this, so you can feel confident that even if you make a mistake, or aren’t aware of best practices, an assistant will keep you on track.</p><p>Before defining a GitHub workflow, let’s install <em>Datree</em> locally. To do that, go to their <a href="https://hub.datree.io/helm-plugin?utm_source=Wojtek&amp;utm_medium=medium+article&amp;utm_campaign=Automating+quality+checks+for+Kubernetes+YAMLs&amp;utm_id=influencer">official website</a> which will guide you how to install the <em>Datree</em> CLI. I’m using Linux (or to be precise, WSL), so only the following command is necessary:</p><pre>&gt; curl https://get.datree.io | /bin/bash</pre><p>After couple of seconds <em>Datree</em> will be installed.</p><p>To test it out, go to the folder where Kubernetes YAML files are located and run <em>Datree</em> test command (in my case I’m using a vanilla K8s file from <a href="https://github.com/wkrzywiec/k8s-helm-helmfile">k8s-helm-helmfile project</a>):</p><pre>&gt; datree test adminer-deployment.yaml</pre><p>where the adminer-deployment.yaml file is a Kubernetes object definition.</p><p>Here is the output that I got:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*Yh-yj6JWuNpNXGR1.png" /></figure><p>As you can see, <em>Datree</em> has prepared a short summary of how many rules this YAML file violates. It provides a very useful explanation with a hint on how to fix them.</p><p>Starting from here, you can work on each issue to make these tests pass. It’s a great way to learn and practise. But what if you deliberately chose not to comply with some of the rules? Luckily, <em>Datree</em> gives a possibility to prepare a custom policy, a set of rules against which YAML files will be checked.</p><p>To set up a policy, you need to go to your dashboard. Your personal link to it is located at the end of each scan, in the summary table, in the last row called See all rules in policy (I&#39;ve marked it in a previous screenshot). It will take you to a login page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/708/0*CBJDGdAJcD6jrjMZ.PNG" /></figure><p>Then you need to login. For convenience, I would suggest using the GitHub account. After successful authorization you will reach a <em>Policies</em> page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*CB90pbFRThMSDSX7.PNG" /></figure><p>Here you can inspect all the rules that you can turn on and off. By default, only some of them are enabled. To activate or deactivate them, use the toggle button visible next to the rule name.</p><p>If a name of a rule is too enigmatic, you can check its details by clicking on its name. It will then show more information and a sample output when this rule is violated.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*S9Yn_csPT9WDOpJT.PNG" /></figure><p>Another cool feature is that you can define your own tips on how to fix a problem. The defaults should be enough, but if you prefer to extend it, view it in your own language, or add a link to a blog post, Stack Overflow discussion or any other online material, you can put it here by simply clicking an <em>Edit</em> button.</p><p>Finally, you can create your own set of rules, known as policies, to run for different applications, environments and stages. Simply click the <em>Create Policy</em> button at the top of the page.</p><p>Let’s now check the second page, available on the left panel, called <em>History</em>. As the name might suggest, here you can see a convenient summary of all previous test runs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*Cd5yhp5MdvkwoIBO.PNG" /></figure><p>Before writing this post, I’d already played around with <em>Datree</em>, which is why I already have several test runs listed in the history panel, but in your case you should have only one.</p><p>That would be it for a quick tour around <em>Datree</em> dashboard!</p><p>Now let’s build a GitHub Actions workflow.</p><p>First, provide your <em>Datree</em> token as an environment variable in a workflow. To achieve this, click on your avatar in the top right corner of the <em>Datree</em> dashboard, then click <strong>Settings</strong>. It’ll guide you to the page where you can find your token.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*5pCrO4-4IM5cR01m.png" /></figure><p>Once you’ve copied the token, go to your GitHub project/repository’s <strong>Settings</strong> page. Then select <strong>Secrets</strong> and click the <strong>New repository secret</strong> button.</p><p>In the <strong>Name</strong> field put DATREE_TOKEN and in the <strong>Value</strong> field a token copied from <em>Datree</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*yqhbA2lKto6DCeqx.PNG" /></figure><p>Now you can move on to the workflow’s definition file and define the first job:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c4fc10a8f1d1e08f33e1e22618003fe4/href">https://medium.com/media/c4fc10a8f1d1e08f33e1e22618003fe4/href</a></iframe><p>It’s called datree, it runs on the latest Ubuntu inside a <a href="https://hub.docker.com/r/dtzar/helm-kubectl/">dtzar/helm-kubectl</a> Docker container. I&#39;ve selected this setup because I would like to run tests against Helm release (and not vanilla K8s as it was done previously). The reason to choose this Docker image is because it contains necessary dependencies (K8s and Helm), so I can skip their installation and make my workflow faster.</p><p>Let’s define the next three steps:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/72acb1be2f0fc7fe9212cebe177a030b/href">https://medium.com/media/72acb1be2f0fc7fe9212cebe177a030b/href</a></iframe><p>With the first one, actions/checkout@v2, you get code from the repository. In step two, you install the <a href="https://hub.datree.io/helm-plugin?utm_source=Wojtek&amp;utm_medium=medium+article&amp;utm_campaign=Automating+quality+checks+for+Kubernetes+YAMLs&amp;utm_id=influencer">Datree Helm plugin</a> to run <em>Datree</em> tests using Helm. In the third step, you run actual tests using a Helm CLI. The DATREE_TOKEN environment variable is added to it so the result will be linked with a <em>Datree</em> account.</p><p>Finally, in an actual run script, I provide the location of a Helm template and the location of testing values.yaml file.</p><p>After that you can commit changes and push it to GitHub. It should trigger a workflow, which will be available in the <strong>Actions</strong> tab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*VrOc7v-XsiaVSGif.PNG" /></figure><p>In my case, several tests are failing, demonstrated by the failing status of a workflow. To find out more, click on the failing job. It will take you to the console output, where you can investigate what went wrong.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*oMm7MJpEPUyVDozn.PNG" /></figure><p>As well as checking policy check result(s) in the workflow’s console, you can go back to <a href="https://app.datree.io/cli/invocations">the History page</a> in the <em>Datree</em> dashboard and analyze errors there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*tSJYQTw4WrLSh1ii.PNG" /></figure><p>If you have a similar screen to mine, stop here, correct mistakes and push changes to GitHub. If you feel that some rules are obsolete for you, turn them off in the <em>Datree</em> dashboard, but do not turn off too many!</p><p>After fixing all the issues and re-running the workflow, the previously marked red exes [X] will become green check marks [V], indicating that the workflow has passed the validation and policy check.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*Hafgt7LGOHEgP_MP.PNG" /></figure><p>And some details about a job:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*_J0oGl103D2sezQ3.PNG" /></figure><p>The successful result will be also visible in the <em>Datree</em> dashboard:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*_-D-lGX1u1jnHzdo.PNG" /></figure><p>Awesome! We can now move on to the next part.</p><h3>Testing on GKE</h3><p>After making sure that templates are okay from <em>Datree</em> ‘s point of view, move to deploying them to a real Kubernetes cluster (which is not production) and then check if everything is working there, e.g. if a website is available over the internet, etc.</p><p>First step would be to have a cluster. I’ve picked <em>Google Kubernetes Engine</em> ( <em>GKE</em>) because of its <a href="https://cloud.google.com/free/docs/gcp-free-tier/#kubernetes-engine">free tier</a>, but if you have your own cluster (on AWS or any other cloud provider) just use it.</p><p>Right now I’ll follow with steps that are necessary to set up a job for <em>GKE</em>, so if you already have a <em>GKE</em> or any other Kubernetes cluster up and running, skip this part.</p><p>Before adding a new job to a workflow, you need to set up couple of things in <em>GKE</em> (these instructions are based on official <a href="https://cloud.google.com/kubernetes-engine/docs/quickstart">GKE Quickstart guide</a>):</p><p>Until now, all configuration was carried out in a web browser, but now you need to move to either <a href="https://cloud.google.com/shell">Google Cloud Shell</a>, or <a href="https://cloud.google.com/sdk/docs/install">install Cloud SDK</a> and follow the instructions in a terminal:</p><p>Set up basic configuration in gcloud tool, like default project, region and zone. In my case, the project name is k8s-helm-helmfile and the region is europe-central2, but for you it may be different.</p><pre>&gt; gcloud config set project k8s-helm-helmfile</pre><pre>&gt; gcloud config set compute/region europe-central2</pre><pre>&gt; gcloud config set compute/zone europe-central2-a</pre><p>Create an <a href="https://cloud.google.com/iam/docs/service-accounts">IAM Service Account</a>, which is an account which will be used in GitHub Action workflow. I’ve called mine helm-github-actions-service:</p><pre>&gt; gcloud iam service-accounts create helm-github-actions-service</pre><p>Get an email from the newly created Service Account. You will need it for the next step:</p><pre>&gt; gcloud iam service-accounts list</pre><p>Assign roles to a Service Account, where &lt;EMAIL&gt; tag is taken from previous step:</p><pre>&gt; gcloud projects add-iam-policy-binding k8s-helm-helmfile \<br>  --member=serviceAccount:&lt;EMAIL&gt; \<br>  --role=roles/container.admin \<br>  --role=roles/storage.admin \<br>  --role=roles/container.clusterAdmin \<br>  --role=roles/iam.serviceAccountUser \<br>  --role=roles/container.developer</pre><p>Export the Service Account Key:</p><pre>&gt; gcloud iam service-accounts keys create key.json --iam-account=&lt;EMAIL&gt;</pre><pre>&gt; export GKE_SA_KEY=$(cat key.json | base64)</pre><p>Add it as a GitHub Secret to a project (the same way as for DATREE_TOKEN) with GKE_SA_KEY as key. To see the value of the exported key you can use the command:</p><pre>&gt; printenv GKE_SA_KEY</pre><p>Everything is set up, now go back to workflow definition where you first created a Kubernetes cluster on <em>GKE</em>, deploy a sample Helm release, and test it.</p><p>First create a new job called gke :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a62160f344c20af7c5f69715ae14ba03/href">https://medium.com/media/a62160f344c20af7c5f69715ae14ba03/href</a></iframe><p>Similarly to the previous example, you have name and runs-on configurations. There is also a needs configuration, which means that in order to run this job, the datree job needs to first be completed successfully. This prevents spinning up clusters and deploying a sample application if something goes wrong during a <em>Datree</em> check. The last part of job configuration are environment variables ( env), which will be used in workflow steps. They&#39;re my GCP project id, K8s cluster name, and my <em>GKE</em> region.</p><p>Moving on to the next steps:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed052cae59f13fa1e1e59f87f46a23f0/href">https://medium.com/media/ed052cae59f13fa1e1e59f87f46a23f0/href</a></iframe><p>The first one is for getting code from the project. The second is to configure (login and set up project) Google Cloud CLI which will be used in following steps.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a2af8391eeff6d4c466bf4cbea2be04e/href">https://medium.com/media/a2af8391eeff6d4c466bf4cbea2be04e/href</a></iframe><p>The above steps create a new GKE cluster and configure kubectl so it’s connected with the newly created cluster. With that, you can move on to the step where an adminer Helm release will be installed:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/89e4e8f830f186a2a8d8606318eb4f55/href">https://medium.com/media/89e4e8f830f186a2a8d8606318eb4f55/href</a></iframe><p>Stop here and analyze what’s going on. First of all, you’re using the <a href="https://github.com/deliverybot/helm">deliverybot/helm</a> GitHub Actions, which provides a convenient way to use Helm. By adding a few parameters, you can deploy an application onto a Kubernetes cluster. The entire list of available parameters can be found on <a href="https://github.com/deliverybot/helm">the official website</a>.</p><p>In the above example, I’ve used the following steps alone:</p><ul><li>release - a release name,</li><li>namespace - specifies the K8s namespace where the app will be installed,</li><li>chart - gives information about the location of a Helm chart,</li><li>helm - indicates which version of Helm will be used,</li><li>value-files - file used to override the default values from a Helm chart, in my case it&#39;s an Adminer&#39;s values.yaml file (the Helm chart I use for testing, which deploys popular database client - <a href="https://www.adminer.org/">Adminer</a>),</li><li>values - this parameter works pretty the same as previous one - it&#39;s used to override the default values from a Helm chart, but instead of doing it with a file, we can directly specify values that need to be overridden; here, I&#39;m overriding only the Service kind, as by default it&#39;s a ClusterIP, but I don&#39;t want to change it in the adminer.yaml file.</li></ul><p>After successful installation, our workflow can proceed with tests. I’ve decided to run a very simple test, which only checks if a page is opening, but you can of course build a more sophisticated test chain.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b53f7a121002451802494b43cdb6c631/href">https://medium.com/media/b53f7a121002451802494b43cdb6c631/href</a></iframe><p>The first line in a testing script is for finding out under which IP address the adminer application is exposed. The second is for debugging, and the last for actual testing.</p><p>Once the tests are done, you need to destroy a cluster:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6cd8281a6b46d17be270ca8dbb0e4007/href">https://medium.com/media/6cd8281a6b46d17be270ca8dbb0e4007/href</a></iframe><p>The if: ${{ always() }} part is very important here. It makes sure that even if any of the previous steps fail, this one will always run. Otherwise you could end up with a bill from Google at the end of a month.</p><p>After committing changes and pushing them to GitHub, a workflow will look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*6hJSNfrdTuFeU2v5.PNG" /></figure><p>First a <em>Datree</em> step is executed, then the installation is done on <em>GKE</em>. To check the details of the second step, click on its name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*M3utA_MsWZ5wo4AB.PNG" /></figure><h3>Conclusion</h3><p>And that’s it for today! I hope that this blog post encourages you to build something like this on your own, give <em>Datree</em> (or any other static code analysis tool) a try, and set up a cluster for automated tests, so you will feel more confident about the changes you made in your code base. It can all be set up and operational in a flash.</p><p><em>Originally published at </em><a href="https://dev.to/wkrzywiec/automating-quality-checks-for-kubernetes-yamls-398"><em>https://dev.to</em></a><em> on September 2, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ceb082800722" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Step-by-step guide how integrate Keycloak with Angular application]]></title>
            <link>https://wkrzywiec.medium.com/step-by-step-guide-how-integrate-keycloak-with-angular-application-d96b05f7dfdd?source=rss-8fb6f1a94f5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/d96b05f7dfdd</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[keycloak]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[Wojciech Krzywiec]]></dc:creator>
            <pubDate>Mon, 22 Mar 2021 02:46:05 GMT</pubDate>
            <atom:updated>2021-03-22T02:46:05.584Z</atom:updated>
            <content:encoded><![CDATA[<p><em>If you’re building a large enterprise application or a one that is </em>publicly <em>available you may want to introduce a concept of users, so that they will be able login to their accounts, put their information and do some stuff with your app, if they’re allowed to. With this blog post I would like to show how it could be implemented in Angular application using OAuth 2.0 and OpenID Connect frameworks an integrate it with a popular, open source identity provider — Keycloak.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IasJ3mZO7qdt0nNv" /><figcaption>Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=medium&amp;utm_medium=referral">Kelly Sikkema</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>This is a fourth and the last part of my series on OAuth 2.0. If you’re not familiar with I would recommend to stop here and go check the first one — <a href="https://medium.com/nerd-for-tech/introduction-to-oauth-2-0-7aa885a3db36">Introduction to OAuth 2.0</a>.</p><p>The aim of this post is to show you a basic set up an <em>Angular</em> application so that it will be integrated with <em>Keycloak</em> and it will be able to consume protected HTTP resource that requires an access token. A full description of <a href="https://medium.com/nerd-for-tech/introduction-to-oauth-2-0-7aa885a3db36">how to set up <em>Keycloak</em> instance</a> and <a href="https://wkrzywiec.medium.com/implementing-oauth-2-0-access-token-validation-with-spring-security-64c797b42b36">how to create a simple protected resource with <em>Spring Boot</em></a> I’ve described in previous two articles. But don’t worry if you don’t want to do that, I’ll explain it later how.</p><h3>The goal</h3><p>For a purpose of this article I’ve prepared a very simple application which could be used to find out information about movies and display them in a table. Application supports two actions — either fetch all the data or select a single movie by its id.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lxzf6YLR6h354eto.png" /></figure><p>All data are served by <em>Java</em> application, which requires to provide a valid <em>OAuth 2.0</em> access token. If anything goes wrong, for example user will not be allowed to consume certain resource a toast message at the bottom of the screen will show up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/447/0*pqVoTJsBugDO3dKN.png" /></figure><p>Apart from frontend app there are two other components in this project. Backend application and Keycloak. First one was already mentioned, in second there are defined users and clients, which are necessary when issuing for an access token.</p><p>Both parts are crucial to make our <em>Angular</em> application work. Both are required to be running. I assume that you might not be interested in going thru my previous tutorials to implement everything by yourself and that’s why I’ve prepared <em>Docker</em> compose file which will allow to start all necessary applications with a single command with my default configuration. Therefore clone my <a href="https://github.com/wkrzywiec/keycloak-security-example">keycloak-security-example</a> repository and checkout to a <a href="https://github.com/wkrzywiec/keycloak-security-example/tree/frontend-start-blog">frontend-start-blog branch</a>. It will be our starting point.</p><p>Once you’ve got that we need to start both services, so go to the terminal and in the root directory of a project run following command:</p><pre>&gt; docker-compose up -d backend</pre><p>In a first run it could take a little while, but after that it should be good. To check if the applications are running you can use command:</p><pre>&gt;  docker ps<br>IMAGE                               STATUS                NAMES<br>keycloak-security-example_backend   Up About a minute     backend<br>jboss/keycloak:11.0.2               Up About a minute     keycloak<br>postgres:13.0-alpine                Up About a minute     postgres</pre><p>All three containers are Up so we&#39;re good to proceed with configuring a <strong>hosts</strong> file on your machine. Its location depends on the OS:</p><ul><li><a href="http://manpages.ubuntu.com/manpages/trusty/man5/hosts.5.html">Linux (Ubuntu)</a></li><li><a href="https://www.groovypost.com/howto/edit-hosts-file-windows-10/">Windows 10</a></li><li><a href="https://www.imore.com/how-edit-your-macs-hosts-file-and-why-you-would-want#page1">Mac</a></li></ul><p>So please find it, open in an admin mode or relevant and add following line:</p><pre>127.0.0.1	keycloak</pre><p>This step is necessary because the address of Keycloak server is different depending on the point of view. From the point of view of an app that is running in the web browser it’ll be http://localhost:8080, but from the perspective of backend application that is running inside the Docker network it will be http://keycloak:8080. To overcome it above config is necessary.</p><p>If you like you can add similar entries for frontend and backend (IP address remains the same), because the side effect of it will be that instead of using for example http://localhost:4200 you can use more meaningful http://frontend:4200. But it&#39;s totally optional and depends on your preferences.</p><h3>Register frontend application in Keycloak</h3><blockquote><em>If you’re running my preconfigured Keycloak instance you can skip this part or just quickly go thru it.</em></blockquote><p>Based on OAuth 2.0 protocol we need to register our application in Keycloak, because only allowed services can issue an access token.</p><p>Therefore, open the Keycloak page — <a href="http://localhost:8080/">http://localhost:8080</a>, select <strong>Administration Console</strong> and provide following credentials:</p><pre>username: admin<br>password: admin</pre><p>After login, in the top right corner there will be displayed a name of a realm into which you’re logged in. Make sure that <em>Test</em> is selected.</p><p>Now go to the <strong>Clients</strong> page (it’s located in the left menu) and click <strong>Create</strong> button located on a right.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wtaNEmWniqcaJ3Ij.png" /></figure><p>In a new view we need to provide only a client name, which will be <strong>frontend</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/464/0*dPe-DiOYmKwbdf2D.png" /></figure><p>After hitting the <strong>Save</strong> button a detailed page for newly created client will show up. To finish configurating <em>frontend</em> client we need first to make sure that <em>Access Type</em> is set to <strong>public</strong>. It indicates that our client is public and will not require secret to get access token. This config is valid for client-side frontends which are not able to securely store secrets for their clients (because the entire code of an application is running in users browser).</p><p>Apart from that we need to set up a <em>Root URL</em> to <a href="http://localhost:4200/"><em>http://localhost:4200</em></a> which is a root URL of our application. Also we need to provide a <em>Valid Redirect URIs</em> which is very important from security point of view, because here we’re defining into which URLs Keycloak can redirect after successful authentication. We should provide here only our application’s URLs, to prevent hackers from doing messy stuff. Therefore here I’ve got 3 entries: http://localhost:80/*, http://localhost:4200/*, <a href="http://localhost/*.">http://localhost/*.</a></p><p>The last thing that needs to be configured is <em>Web Origins</em> parameter and here I’m providing + which will handle the CORS problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/965/0*LM41MOMpEbfEzFfN.png" /></figure><p>After that click <strong>Save</strong> button at the bottom of the screen to finish Keycloak configuration (all other steps, like user and roles definition was already covered in previous blog posts).</p><h3>Main view of a page</h3><p>Now we can deep dive into Angular project. It has only one component (apart from AppComponent) - ContentComponent.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec5537d4de71def295a4ec0f87f37b9d/href">https://medium.com/media/ec5537d4de71def295a4ec0f87f37b9d/href</a></iframe><p>To keep you sane and to keep this post short I’ll skip showing HTML and CSS files, as the aim of the project is not to look nice.</p><p>From the above code you can see that in order to connect to the backend application it uses the MovieBackendService which encapsulates HTTP requests:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ed1ffe28ed5b04b37a28ff155a034cb/href">https://medium.com/media/1ed1ffe28ed5b04b37a28ff155a034cb/href</a></iframe><p>Again, fairly simple.</p><p>To make it work I’ve also set up a proxy configuration, both for development and production mode. For a first one I’ve created a proxy.conf.dev.json file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a04d040caf857bc24d8ac5fb42cb0a51/href">https://medium.com/media/a04d040caf857bc24d8ac5fb42cb0a51/href</a></iframe><p>The target field indicates under which address the backend service is available. To apply it I’ve also modified the start script in package.json file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/82c8eb604de814faf59adb2dde661b35/href">https://medium.com/media/82c8eb604de814faf59adb2dde661b35/href</a></iframe><p>For production proxy config I’ve created the default.conf file, which is a configuration of <em>Nginx</em> server:</p><pre>server {</pre><pre>    listen 80;<br>    server_name frontend;<br>    root /usr/share/nginx/html;<br>    index index.html index.html;</pre><pre>    location /movies {<br>        proxy_pass http://backend:9000/movies;</pre><pre>    }</pre><pre>    location / {<br>        try_files $uri $uri/ /index.html;<br>    }<br>}</pre><p>The http://backend:9000/movies is an address of the backend service (if a backend service is running in the same Docker network, or in the same Kubernetes cluster with the same Service name).</p><p>Having all of that we can now proceed with adding and configuring Keycloak libraries.</p><h3>Basic configuration of Keycloak libraries</h3><p>First step would be to add <a href="https://github.com/mauriciovigolo/keycloak-angular">keycloak-angular</a> dependencies to the project, therefore in a terminal run following command:</p><pre>&gt; npm install keycloak-angular keycloak-js</pre><p>Having it we now need to config it with our application. What is important here, before running our own code first we need to authenticate a user, show Keycloak’s login page and so on. To achieve it we will use an Angular’s special InjectionToken called APP_INITIALIZER. A function, which will be assigned to this token will be executed before loading the rest of an application.</p><p>To create a custom function, that we will than assign to APP_INITIALIZER just run this command:</p><pre>&gt; ng g class init/keycloak-init --type=factory --skip-tests<br>CREATE src/app/init/keycloak-init.factory.ts (22 bytes)</pre><p>A new file was created. Open it and paste following content:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f03699e9935bfe154614bd67b8fcdf6f/href">https://medium.com/media/f03699e9935bfe154614bd67b8fcdf6f/href</a></iframe><p>Above function takes as an argument a KeycloakService which is an object from keycloak-angular library so that we can use it in a body of a function to config it by providing URL to the server, realm name and application&#39;s client id which was created a step before.</p><p>For now all values are hardcoded here, which of course is not good, but we’ll tackle this problem later on.</p><p>Next we need to register this function in the app.module.ts file, as a provider:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d71875c40d6b6f4dba77f599f8cfd3c/href">https://medium.com/media/4d71875c40d6b6f4dba77f599f8cfd3c/href</a></iframe><p>Take a closer look in the imports section. There is KeycloakAngularModule added there.</p><p>Next step would be to create a <em>Guard</em> that will protect routes in an application. The library that we’re using is already providing a preconfigured, abstract KeycloakAuthGuard class from which we will extend ours. But first let&#39;s create one:</p><pre>&gt; ng g guard guard/auth --skip-tests</pre><pre>? Which interfaces would you like to implement? CanActivate<br>CREATE src/app/guard/auth.guard.ts (458 bytes)</pre><p>The content of this <em>Guard</em> would be:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/788e22534e39ec0f67ada787ca0988a4/href">https://medium.com/media/788e22534e39ec0f67ada787ca0988a4/href</a></iframe><p>In the isAccessAllowed method we&#39;re adding the piece that will redirect a user to a login page if she/he is not authenticated yet.</p><p>To finish this part, the only thing to do is to add this <em>Guard</em> to the app-routing.module.ts.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8266eedf4a6ea5ef10f132fed299ae56/href">https://medium.com/media/8266eedf4a6ea5ef10f132fed299ae56/href</a></iframe><p>Having all of that we can test it! Make sure that both backend and Keycloak services needs to be running ( docker-compose up -d backend ) and run the command:</p><pre>&gt; npm start</pre><pre>&gt; frontend@0.0.0 start .\keycloak-security-example\frontend<br>&gt; ng serve --proxy-config src/assets/proxy.conf.dev.json</pre><pre>- Generating browser application bundles...[HPM] Proxy created: /movies  -&gt;  http://localhost:9000<br>[HPM] Subscribed to http-proxy events:  [ &#39;error&#39;, &#39;close&#39; ]<br>√ Browser application bundle generation complete.</pre><pre>Initial Chunk Files   | Names         |      Size<br>vendor.js             | vendor        |   5.11 MB<br>styles.css, styles.js | styles        | 562.38 kB<br>polyfills.js          | polyfills     | 493.08 kB<br>main.js               | main          |  40.76 kB<br>runtime.js            | runtime       |   6.15 kB</pre><pre>                      | Initial Total |   6.18 MB</pre><pre>Build at: 2021-02-20T15:55:55.362Z - Hash: 360dd85cc05c08ec0698 - Time: 14478ms</pre><pre>** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **<br></pre><pre>√ Compiled successfully.</pre><p>Now if you open the http://localhost:4200/ page you should be redirected to the login page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*L7sKH8cNpr_wVLpv.png" /></figure><p>And provide credentials:</p><pre>username: han/luke<br>password: password</pre><p>After that you should be able to enter the main page! If you now try to play around with an app, by clicking All movies button or selecting movie by id, in one of the cases you&#39;ll get this kind of an error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/637/0*2nO74c18BbiYBUM2.png" /></figure><p>This toast message inform the user that she/he doesn’t have sufficient roles. But how application knows that?</p><p>First of all after successful login OAuth2.0 tokens were fetched from the Keycloak server and stored in your web browser. Now if there is any request made to the backend service our great Keycloak library is adding the Authorization HTTP header with an access token. And such is required to be consumed by the backend application. It validates a content of a token and returns OK response if everything is fine, or returns an error if token is corrupted or user is not allowed to consume specific endpoint (because she/he doesn&#39;t have sufficient roles).</p><h3>Productionize Keycloak configuration</h3><p>So far so good, we’ve got a working piece of code. But there is one problem with this approach. If we would need to change the Keycloak’s URL, realm name or client (development, test and production environment can be different) we would be forced to change it in the code for each version, as all these values are hardcoded.</p><p>To overcome this problem we need to externalize configuration of an app. My approach would be to pass those values from the environment variable during startup of an application. Because we’re using Docker it’s quite easy to achieve.</p><p>First step is to create separate configuration files for two cases — development and production. Therefore in the <strong>assets</strong> folder create a new <strong>config</strong> folder inside of which place these two following files:</p><ul><li><strong>config.dev.json</strong></li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/79b45d7c2f0bae5e14d0153ca8c51d4b/href">https://medium.com/media/79b45d7c2f0bae5e14d0153ca8c51d4b/href</a></iframe><ul><li><strong>config.prod.json</strong></li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7bf57b81657f86278aa6d47ed1d63189/href">https://medium.com/media/7bf57b81657f86278aa6d47ed1d63189/href</a></iframe><p>In the latter config file there are no hardcode values, just a placeholder for environment variables, which will be replaced during the startup of an application.</p><p>Next step would be to make use of these two configuration. Therefore got to <strong>environments</strong> directory and add a configFile value which will point out to a different config file.</p><ul><li><strong>environment.ts</strong></li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e670283bb871f9fe48c455cee9ad706b/href">https://medium.com/media/e670283bb871f9fe48c455cee9ad706b/href</a></iframe><ul><li><strong>environment.prod.ts</strong></li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ea52186435c08b55132411a31549432a/href">https://medium.com/media/ea52186435c08b55132411a31549432a/href</a></iframe><p>Also create a new file, <strong>environment.dev.ts</strong> which will be the same as the default one:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d0348e5dcd891651bdb70114dbd6798/href">https://medium.com/media/3d0348e5dcd891651bdb70114dbd6798/href</a></iframe><p>Having that we could now move on to the code and make use of it, but after the <em>dev</em> environment is not fully setup. We need to add this new environment to an Angular configuration.</p><p>Therefore open the <strong>angular.json</strong> file and locate the projects.frontend.architect.build.configurations section and add dev configuration which will be replacing the default environment.ts file with environment.dev.ts:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f0fa39cb3584f47869a84c75f08bc2f3/href">https://medium.com/media/f0fa39cb3584f47869a84c75f08bc2f3/href</a></iframe><p>In the same file, scroll down a little bit to the serve section and in configurations add new dev entry with browserTarget:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d2beded23b1e6021d80fee0b3dd62a58/href">https://medium.com/media/d2beded23b1e6021d80fee0b3dd62a58/href</a></iframe><p>Finally we can go to the <strong>package.json</strong> file in which we can specify that when we want to run the start script we would like to enable the dev profile, by adding the -c dev parameter.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8ec4f0f966942b626922b25da3c86cf4/href">https://medium.com/media/8ec4f0f966942b626922b25da3c86cf4/href</a></iframe><p>After this change once we run the npm start the Angular app will be build with dev configuration.</p><p>If we run an application right now nothing will change, because we only registered a new application profile, but haven’t make a use of it in the code. Therefore let’s now focus on the code itself.</p><p>Right now we’re initializing KeycloakService before the entire application. What we need to do now is to load configuration before KeycloakService. To do that first let&#39;s create a ConfigInitService which will load a configuration file and assign it&#39;s values to a variable.</p><pre>&gt; ng g service init/config-init --skip-tests<br>CREATE src/app/init/config-init.service.ts (140 bytes)</pre><p>And the content of this service class in which we’re using the environment’s value that points out to the correct configuration file (dev or prod).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7254d3f22dfa057651268734c8f03dde/href">https://medium.com/media/7254d3f22dfa057651268734c8f03dde/href</a></iframe><p>Next, we need to go back to the <em>keycloak-init.factory.ts</em> and adjust it so that getConfig() method of above service is invoked before KeycloakService initialization.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b13751ec1d1ef862c8933d3953c525db/href">https://medium.com/media/b13751ec1d1ef862c8933d3953c525db/href</a></iframe><p>This time a code is a little bit complicated. confiService is returning the config object from which we can get necessary values.</p><p>Last thing to do in the app code is to register a ConfigInitService in app.module.ts and pass it to the APP_INITIALIZER.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/81c966bd703ff86fae837d9bf723e27d/href">https://medium.com/media/81c966bd703ff86fae837d9bf723e27d/href</a></iframe><p>If we run the npm start command the app will start up and works as previously. Awesome, we&#39;re in the same situation as we were previously 😏.</p><p>Let’s move forward and change the Docker image definition. If you look at the Dockerfile for frontend app it looks pretty straightforward:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d093b88b283d8f764337983ffef8092/href">https://medium.com/media/1d093b88b283d8f764337983ffef8092/href</a></iframe><p>It’s a two stage Dockerfile, in first step a project is build to HTML, CSS and JSes and in the second step all these files are copied to an <em>NGINX</em> server together with simple server configuration in <em>default.conf</em> file.</p><p>What we need to change in above file is to somehow replace placeholders from <em>config.prod.json</em> with relevant environment variables that will injected into Docker container during startup. Apart from that it would be to do similar thing with <em>default.conf</em> file in which we’ve got a proxy_pass defined which routes traffic to the backend service. Instead of hardcoding the backend URL we can allow to dynamically assigning it during container startup.</p><p>Therefore in default.conf file we need to replace the http://backend:900 value to ${BACKEND_BASE_PATH}.</p><pre>server {<br>    listen 80;<br>    server_name frontend;<br>    root /usr/share/nginx/html;<br>    index index.html index.html;</pre><pre>    location /movies {<br>        proxy_pass ${BACKEND_BASE_PATH}/movies;<br>    }</pre><pre>    location / {<br>        try_files $uri $uri/ /index.html;<br>    }<br>}</pre><p>But we still have a problem, how to replace those placeholders with values from environment variables? Nothing simpler, we can use <a href="https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html">envsubt</a> bash command.</p><p>First, let’s create a bash script located in the root folder of frontend project and call it <strong>replace_placeholders.sh</strong>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d157c2f2ec8ec5f825a28ad3309c5a76/href">https://medium.com/media/d157c2f2ec8ec5f825a28ad3309c5a76/href</a></iframe><p>In a first line we’re replacing the placeholders from <strong>config.prod.json</strong> and as a result it generates a <strong>config.json</strong> file with values from environment variables (which holds Keycloak url, realm and client info). With the second line we’re replacing only one placeholder with a base url to the backend application in <strong>default.conf</strong> — NGINX server configuration.</p><p>Having it setup we can change <strong>Dockerfile</strong> definition, so that it will copy a script to a container and run it during the startup.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58b389ef41b4a9979b96619e60f4d583/href">https://medium.com/media/58b389ef41b4a9979b96619e60f4d583/href</a></iframe><p>Finally to run the application we need to declare those environment variables. Depending on how you would like to run the application there are many ways to achieve it. For example they can be declared in OS, passing them as parameters using Docker commands, using Docker Compose file or be declared in Kubernetes Deployment or ConfigMap.</p><p>For me the most convenient is to use Docker Compose (<em>docker-compose.yaml</em> file located in the project root)</p><pre>version: &quot;3.8&quot;<br><br>services:<br>  <br>  frontend:<br>    build: ./frontend<br>    container_name: frontend<br>    ports:<br>      - 80:80<br>    environment: <br>      - KEYCLOAK_URL=http://keycloak:8080<br>      - KEYCLOAK_REALM=test<br>      - KEYCLOAK_CLIENT_ID=frontend<br>      - BACKEND_BASE_PATH=http://backend:9000<br>    depends_on:<br>      - keycloak<br>      - backend<br><br>  <em># definitions of other services</em></pre><p>Now run the following command and a Docker container will be created and then started.</p><pre>&gt; docker-compose up -d frontend</pre><p>To check if everything is working we can list Docker containers:</p><pre>&gt; docker ps<br><br>CONTAINER ID    STATUS          NAMES<br>1840d7564aeb   Up 46 seconds    frontend<br>cba18013881c   Up 47 seconds    backend<br>01f15608d210   Up 47 seconds    keycloak<br>ac67959019f9   Up 48 seconds    postgres</pre><p>Than go to a web browser and type http://localhost so it will redirect you to the login page and once you login you can play around with an app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kRHtsUFMGyewlthP.png" /></figure><p>That&#39;s it for today and for entire series about OAuth 2.0 framework. I hope you&#39;ve enjoyed and learnt some new things. Please let me know in the comments what do you think.</p><p>The entire code for this project can be found in my GitHub repository:</p><p><a href="https://github.com/wkrzywiec/keycloak-security-example">wkrzywiec/keycloak-security-example</a></p><p>Here is the list of all other posts in this series:</p><ul><li><a href="https://medium.com/nerd-for-tech/introduction-to-oauth-2-0-7aa885a3db36">Introduction to OAuth 2.0</a></li><li><a href="https://wkrzywiec.medium.com/create-and-configure-keycloak-oauth-2-0-authorization-server-f75e2f6f6046">Create and configure Keycloak OAuth 2.0 authorization server</a></li><li><a href="https://wkrzywiec.medium.com/implementing-oauth-2-0-access-token-validation-with-spring-security-64c797b42b36">Implementing OAuth 2.0 access token validation with Spring Security</a></li></ul><h3>References</h3><ul><li><a href="https://github.com/mauriciovigolo/keycloak-angular">mauriciovigolo/keycloak-angular</a></li><li><a href="https://medium.com/@ryanchenkie_40935/angular-authentication-using-route-guards-bf7a4ca13ae3">Angular Authentication: Using Route Guards</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d96b05f7dfdd" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>