<?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[Bforbank Tech - Medium]]></title>
        <description><![CDATA[We share here articals about technical subjects - Medium]]></description>
        <link>https://medium.com/bforbank-tech?source=rss----13f907e6a3b---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Bforbank Tech - Medium</title>
            <link>https://medium.com/bforbank-tech?source=rss----13f907e6a3b---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 19 May 2026 12:23:00 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/bforbank-tech" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Stop Treating Dialogs as Events: A State-Driven Approach in Jetpack Compose]]></title>
            <link>https://medium.com/bforbank-tech/stop-treating-dialogs-as-events-a-state-driven-approach-in-jetpack-compose-170507ad21fe?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/170507ad21fe</guid>
            <category><![CDATA[state-management]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Nihel GUEDRI]]></dc:creator>
            <pubDate>Mon, 05 Jan 2026 09:42:07 GMT</pubDate>
            <atom:updated>2026-01-05T09:42:06.615Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>How modeling dialogs as state leads to cleaner, safer, and more predictable Android UIs.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*12Wv7TMiOWGkC_0FbZ23bA.png" /></figure><p>Jetpack Compose promotes a declarative way of building user interfaces: the UI is a direct representation of the current state.</p><p>However, when dealing with <strong>dialogs, alerts, or pop-ins</strong>, many Android developers still rely on event-based patterns that do not fully align with Compose’s philosophy.</p><p>In this article, we will explore a <strong>state-driven approach</strong> to managing conditional dialogs in Jetpack Compose, using simple and generic examples.</p><h3>The Common Pitfall: Treating Dialogs as Events</h3><p>A common implementation looks like this:</p><p>• The ViewModel emits a “show dialog” event</p><p>• The UI listens and displays the dialog once</p><p>This is often implemented using:</p><p>• SingleLiveEvent</p><p>• SharedFlow</p><p>• Channel</p><p>While this works, it introduces several drawbacks:</p><p>• Events can be lost during configuration changes</p><p>• Dialogs may reappear unexpectedly after recomposition</p><p>• The UI becomes harder to test</p><p>• Business rules leak into Composables</p><p>With Compose, where recomposition is frequent and intentional, these issues become more noticeable.</p><h3>Rethinking Dialogs as State</h3><p>Instead of asking:</p><p>“<em>When should I show this dialog?</em>”</p><p>We should ask:</p><p>“<em>In which UI state should this dialog be visible?</em>”</p><p>If the dialog is visible, it should be <strong>explicitly represented in the UI state</strong>.</p><p>This simple mindset shift leads to cleaner and more predictable code.</p><h3>Defining the UI State</h3><p>Let’s start with a simple screen where a user completes a profile.</p><p>In some cases, we want to display a warning dialog if certain conditions are met.</p><pre>data class ProfileUiState(<br>  val user: UserUiModel?,<br>  val warning: WarningUiModel?,<br>  val isLoading: Boolean = false<br>)</pre><p>Here:</p><ul><li>warning == null → no dialog</li><li>warning != null → dialog is displayed</li></ul><h3>Modeling Dialog Content</h3><p>We model dialog content using a sealed interface:</p><pre>sealed interface WarningUiModel {<br>  val title: String<br>  val message: String<br><br><br>  data class MissingEmail(<br>    override val title: String,<br>    override val message: String<br>  ) : WarningUiModel<br><br><br>  data class IncompleteProfile(<br>    override val title: String,<br>    override val message: String<br>  ) : WarningUiModel<br><br>}</pre><p>This makes the UI expressive and type-safe, while keeping business logic out of the Composable.</p><h3>Showing the Dialog in Compose</h3><p>In the Composable, we simply react to the state:</p><pre>@Composable<br>fun ProfileScreen(<br>  uiState: ProfileUiState,<br>  onConfirmWarning: () -&gt; Unit<br>) {<br><br>  uiState.warning?.let { warning -&gt;<br>    AlertDialog(<br>      onDismissRequest = {},<br>      title = { Text(warning.title) },<br>      text = { Text(warning.message) },<br>      confirmButton = {<br>        TextButton(onClick = onConfirmWarning) {<br>          Text(&quot;OK&quot;)<br>        }<br>      }<br>    )<br>  }<br>  <br>  // Rest of the screen content<br><br>}</pre><p>There is:</p><p>• No event observer</p><p>• No side effect</p><p>• No manual dialog control</p><p>The UI simply reflects the current state.</p><h3>Selecting the Appropriate Warning in the ViewModel</h3><p>The ViewModel decides <strong>which warning should be shown</strong>, based on domain rules.</p><pre>private fun selectWarning(user: UserUiModel): WarningUiModel? {<br>  return when {<br>    user.email.isNullOrBlank() -&gt;<br>      WarningUiModel.MissingEmail(<br>        title = &quot;Missing information&quot;,<br>        message = &quot;Please add an email address to continue.&quot;<br>      )<br>    <br>    !user.isProfileComplete -&gt;<br>      WarningUiModel.IncompleteProfile(<br>        title = &quot;Profile incomplete&quot;,<br>        message = &quot;Some required fields are still missing.&quot;<br>      )<br>    <br>    else -&gt; null<br>  }<br>}</pre><p>Only one warning is exposed at a time, keeping the UI logic simple.</p><h3>Consuming the Warning</h3><p>When the user confirms the dialog, the ViewModel updates the state:</p><pre>fun onWarningConfirmed() {<br>  _uiState.update { currentState -&gt;<br>    currentState.copy(warning = null)<br>  }<br>}</pre><p>As soon as warning becomes null, the dialog disappears automatically.</p><h3>Why This Pattern Works Well</h3><p>This approach offers several advantages:</p><ul><li>Dialog visibility is explicit and deterministic</li><li>No lost or duplicated events</li><li>Easier ViewModel unit testing</li><li>Cleaner Composables</li><li>Business logic stays in the ViewModel</li></ul><p>Most importantly, it embraces <strong>Compose’s declarative nature</strong>.</p><h3><strong>When Are Events Still Useful?</strong></h3><p>Event-based approaches are still relevant for:</p><p>• Navigation actions</p><p>• Snackbars and toasts</p><p>• Analytics or logging side effects</p><p>But for dialogs that represent <strong>business rules or user decisions</strong>, modeling them as state is often the better choice.</p><h3>Conclusion</h3><p>In Jetpack Compose, dialogs do not need to be treated as special cases.</p><p>By modeling them as part of the UI state, we gain clarity, predictability, and robustness.</p><p>This <strong>state-driven approach</strong> scales well as applications grow and helps teams maintain a clean separation between UI and business logic.</p><p>If you are building Compose screens today, this pattern is well worth considering.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=170507ad21fe" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/stop-treating-dialogs-as-events-a-state-driven-approach-in-jetpack-compose-170507ad21fe">Stop Treating Dialogs as Events: A State-Driven Approach in Jetpack Compose</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden Mathematics of Randomness: Demystifying Random Functions in Programming]]></title>
            <link>https://medium.com/bforbank-tech/the-hidden-mathematics-of-randomness-demystifying-random-functions-in-programming-e32a762a86c2?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/e32a762a86c2</guid>
            <category><![CDATA[random]]></category>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[lcg]]></category>
            <category><![CDATA[prng]]></category>
            <dc:creator><![CDATA[Bacem BEN AFIA]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 10:15:54 GMT</pubDate>
            <atom:updated>2026-01-02T10:15:52.769Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*WPNV0lytazCGqv3gMm2Zlw@2x.jpeg" /></figure><p>Randomness is everywhere in modern software – from the shuffle button in your music player to the dice rolls in your favorite video game. But have you ever wondered what’s really happening when you call Math.random() or use a random function in your code? The truth is, these «random» numbers are anything but random. They’re the product of sophisticated mathematical algorithms that create the illusion of randomness. Let’s dive into the fascinating world of pseudorandom number generators and uncover the mathematics that powers modern computing.</p><h3>The Mathematical Foundation: Probability Theory Meets Computation</h3><p>At its core, random number generation in computers is built on probability theory. In mathematics, we define probability through the concept of a probability space, which consists of three components: a sample space (all possible outcomes), a set of events (subsets of outcomes), and a probability measure that assigns likelihoods to events.</p><p>However, computers are deterministic machines – they follow precise instructions and produce predictable outputs. This creates an interesting paradox: how can a deterministic system produce random behavior? The answer lies in <strong>P</strong>seudo<strong>R</strong>andom <strong>N</strong>umber <strong>G</strong>enerators (PRNGs), which use deterministic algorithms to produce sequences that appear random for practical purposes.</p><p>The foundation of computational randomness rests on the concept of uniform distribution. A truly uniform random variable has the property that any value within its range is equally likely. For continuous variables, this means the probability density function is flat across the interval [0,1). For discrete variables, each possible outcome has equal probability.</p><h3>The Algorithms Behind the Magic</h3><p><strong>Linear Congruential Generator (LCG): The Simple Workhorse</strong></p><p>The most fundamental random number algorithm is the Linear Congruential Generator, which follows a beautifully simple mathematical formula:</p><p>Xᵢ₊₁ = (a × Xᵢ + c) mod m</p><p>Where:</p><ul><li>Xᵢ is the current random number</li><li>a is the multiplier (0 &lt; a &lt; m)</li><li>c is the increment (0 ≤ c &lt; m)</li><li>m is the modulus (m &gt; 0)</li><li>X₀ is the seed value</li></ul><p>This algorithm works by performing modular arithmetic on a linear transformation of the previous value. The choice of parameters is crucial: poor choices can lead to short periods and obvious patterns, while good choices can create sequences that appear quite random.</p><p><em>For example, with parameters a=3, c=3, m=7, and seed X₀=5, we get the sequence: 5, 4, 1, 6, 0, 3, 5, 4, 1, 6… Notice how it eventually repeats – this is characteristic of all pseudorandom generators.</em></p><h3>Mersenne Twister: The Modern Standard</h3><p>Most modern programming languages use a more sophisticated algorithm called the Mersenne Twister. Developed in 1997 by Makoto Matsumoto and Takuji Nishimura, this algorithm provides several advantages over simpler LCGs:</p><ul><li><strong>Incredibly long period</strong>: 2¹⁹⁹³⁷ – 1 (a Mersenne prime)</li><li><strong>High-dimensional equidistribution</strong>: uniform in 623 dimensions</li><li><strong>Efficient implementation</strong>: uses bitwise operations for speed</li></ul><p>The Mersenne Twister works with a state array of 624 32-bit integers. It uses complex bit manipulation operations including shifts, XORs, and masks to transform the internal state and produce random numbers. The algorithm’s name comes from its period length being a Mersenne prime.</p><p>The generation process involves several steps:</p><ol><li><strong>Twisting</strong>: Transforming the state array using bitwise operations</li><li><strong>Tempering</strong>: Applying additional transformations to improve randomness</li><li><strong>Output</strong>: Producing the final random number</li></ol><h3>Cryptographically Secure PRNGs: When Security Matters</h3><p>For applications requiring security, such as cryptography, standard PRNGs are insufficient because they’re predictable if you know the algorithm and internal state.</p><p>Cryptographically Secure PRNGs (CSPRNGs) address this by providing two crucial properties:</p><ol><li>Next-bit test: Given any sequence of bits, there’s no polynomial-time algorithm that can predict the next bit with probability significantly greater than 50%</li><li>State compromise extension: Even if an attacker learns the internal state, they cannot determine previous outputs</li><li>CSPRNGs typically use cryptographic hash functions or block ciphers to mix entropy and produce unpredictable outputs. They gather entropy from various system sources like mouse movements, keyboard timing, and disk I/O patterns.</li></ol><h3>The Critical Role of Seeds</h3><p>Every pseudorandom generator needs a starting point called a seed. The seed value is crucial because:</p><ul><li><strong>Deterministic behavior</strong>: The same seed produces the same sequence</li><li><strong>Reproducibility:</strong> Enables debugging and scientific reproducibility</li><li><strong>Security implications:</strong> Predictable seeds compromise security</li></ul><p>Good seed selection involves:</p><ul><li><strong>Uniqueness</strong>: Avoiding reuse across different instances</li><li><strong>Randomness</strong>: Ensuring no patterns or correlations</li><li><strong>Adequate</strong> <strong>size</strong>: Matching the PRNG’s state size</li></ul><p>Common seed sources include system time, hardware random number generators, user input timing, and environmental noise. For cryptographic applications, seeds must be truly unpredictable.</p><h3>Real-World Applications</h3><p>Random number generation powers countless applications across industries:</p><p><strong>Security and Cryptography</strong></p><ul><li>Key generation: Creating secure encryption keys</li><li>Token generation: Producing session tokens and CSRF protection</li><li>Password generation: Creating strong, random passwords</li><li>Digital signatures: Ensuring uniqueness in cryptographic operations</li></ul><p><strong>Gaming and Entertainment</strong></p><ul><li>Procedural content generation: Creating infinite worlds in games like Minecraft</li><li>AI behavior: Making NPCs unpredictable and interesting</li><li>Loot systems: Determining item drops and rarity</li><li>Card shuffling: Ensuring fair play in digital card games</li></ul><p><strong>Scientific Computing</strong></p><ul><li>Monte Carlo simulations: Modeling complex systems in physics, finance, and engineering</li><li>Statistical sampling: Drawing representative samples from populations</li><li>Optimization algorithms: Stochastic optimization and genetic algorithms</li><li>Machine learning: Random initialization of neural networks, data shuffling</li></ul><p><strong>Simulation and Modeling</strong></p><ul><li>Weather prediction: Ensemble forecasting methods</li><li>Financial modeling: Risk assessment and option pricing</li><li>Traffic simulation: Urban planning and optimization</li><li>Queueing theory: Network and service optimization</li></ul><h3>The Future of Randomness</h3><p>As computing evolves, so does random number generation. Emerging trends include:</p><ul><li>Quantum random number generators: Using quantum phenomena for true randomness</li><li>Hardware-based generators: Physical and Environnemental Entropy Sources for high-quality randomness (a video worth watching: <a href="https://www.youtube.com/watch?v=1cUUfMeOijg">The Lava Lamps That Help Keep The Internet Secure</a>)</li><li>Parallel generators: Algorithms optimized for multi-core and GPU environments</li><li>Machine learning approaches: Using neural networks to improve randomness quality</li></ul><h3>Conclusion</h3><p>Random number generation represents a beautiful intersection of mathematics, computer science, and practical engineering. While the numbers aren’t truly random, the sophisticated algorithms behind them serve our needs remarkably well. Understanding these algorithms helps us make better choices about which generators to use, how to seed them properly, and what limitations to consider.</p><p>Whether you’re securing sensitive data, building a game or conducting scientific research, the humble random function is working behind the scenes, creating the illusion of chaos from mathematical order. The next time you call <strong>Math.random()</strong> or use a random function, remember: you’re not just getting a random number – you’re benefiting from decades of mathematical research and engineering refinement.</p><p>The mathematics of randomness reminds us that sometimes, the most useful solutions come from embracing the tension between what we want (true randomness) and what we can actually achieve (pseudo-randomness). In this gap between ideal and reality, we find practical tools that power modern computing.</p><h3>Bonus</h3><p>Different programming languages implement random number generation with varying approaches:</p><ul><li>Python: Uses the Mersenne Twister as its default generator, accessible through the random module. Provides both simple functions and a class-based interface.</li><li>Java: Offers multiple generators including Random (LCG-based) and SecureRandom (CSPRNG). The newer SplittableRandom provides better performance for parallel applications.</li><li>C++: Prior to C++11, only provided a basic rand() function. Modern C++ includes a comprehensive &lt;random&gt; header with multiple engines and distributions.</li><li>JavaScript: Provides Math.random() which returns values in [0,1). The implementation varies by browser but typically uses high-quality algorithms.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e32a762a86c2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/the-hidden-mathematics-of-randomness-demystifying-random-functions-in-programming-e32a762a86c2">The Hidden Mathematics of Randomness: Demystifying Random Functions in Programming</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automated Integration testing — using Karate & GitLab CI]]></title>
            <link>https://medium.com/bforbank-tech/automated-integration-testing-using-karate-gitlab-ci-a740131f11b1?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/a740131f11b1</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[deployment-pipelines]]></category>
            <category><![CDATA[deployment-automation]]></category>
            <category><![CDATA[karate]]></category>
            <category><![CDATA[gitlab-ci]]></category>
            <dc:creator><![CDATA[Rayen Rejeb]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 10:08:46 GMT</pubDate>
            <atom:updated>2026-01-02T10:08:46.254Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Automated Integration testing — using Karate &amp; GitLab CI</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XiEPXheKvxbCWL-pmnf51A.png" /><figcaption>Testing interactions between multiple units or modules</figcaption></figure><p>Well, none of us wants to see our software crash in production after delivering a new feature to one component or service. In today’s complex software landscape, ensuring that components of a system interact seamlessly is crucial for delivering high-quality applications. Integration testing plays a vital role in validating these interactions, preventing costly defects that can arise from unexpected behaviors or misconfigurations.</p><h3>Integration Testing</h3><p>It is a type of testing where software components are incrementally integrated and then tested. The main goal is to guarantee, for each use case, that your application behave as expected and satisfies the intended criteria by testing the interactions between components or services.</p><h4>Why Integration Testing matters ?</h4><ul><li><strong>Early detection of defects</strong><br>Identify issues at an earlier stage of development, reducing the cost and time required for remediation.</li><li><strong>Improved system stability<br></strong>By verifying that components work together as expected and ensuring the overall stability and reliability of the system after delivering new features.</li><li><strong>Enhanced collaboration and documentation<br></strong>It fosters collaboration among development teams by providing a shared understanding of how components interact and illustrating different scenarios.</li></ul><h4>Challenges of manual Integrating testing</h4><p>Executing integration tests manually is really <strong>Time-consuming</strong>. Manually testing all possible interactions with a certain component may take a lot of time and can be extremely <strong>error-prone </strong>which can lead to inconsistent results and missed scenarios. Furthermore, as the system grows in complexity, maintaining manual test cases becomes increasingly challenging.</p><h4>The power of Automation</h4><p>To overcome those challenges, of manually testing, automation is essential. The idea behind this concept is to integrate the execution of a set of tests while delivering new features. This was, we can:</p><ul><li><strong>Improve efficiency:</strong> Reduce the time and effort required for testing.</li><li><strong>Ensure consistency: </strong>Guarantee that tests are executed the same way every time for each pre-defined use case.</li><li><strong>Increase test coverage:</strong> Execute numerous test cases more frequently.</li></ul><p>One of the best ways to apply these concepts is by relying on another concept: <strong>continuous integration </strong>(CI). Integrate testing into your development workflow can help us catch issues early in development stage</p><p>In the next section, we will discover how to <strong>write</strong>, <strong>automate</strong> and <strong>integrate</strong> integration tests in development workflow using <strong>Karate</strong> and <strong>Gitlab CI</strong>.</p><h4>What is Karate ?</h4><p>Karate is a behavior-driven development (BDD) framework that simplifies API testing. It combines the simplicity of <strong>Cucumber</strong> with the power of <strong>Karate DSL </strong>which allows for creating powerful API test cases in a BDD format using Gherkin syntax, making it easy to write executable specifications that can be used for testing components and for different use cases.</p><blockquote>Karate is the only open-source tool to combine API test-automation, <a href="https://github.com/karatelabs/karate/blob/master/karate-netty">mocks</a>, <a href="https://github.com/karatelabs/karate/blob/master/karate-gatling">performance-testing</a> and even <a href="https://github.com/karatelabs/karate/blob/master/karate-core">UI automation</a> into a single, <em>unified</em> framework. The syntax is language-neutral, and easy for even non-programmers. Assertions and HTML reports are built-in, and you can run tests in parallel for speed.<br><a href="https://www.karatelabs.io/api-testing#:~:text=Karate%20is%20the%20only%20open,easy%20for%20even%20non%2Dprogrammers.">karatelabs.io</a></blockquote><p>It is a versatile, powerful and well documented tool for API testing, making it a popular choice among developers and testers. A full documentation is available on <a href="https://github.com/karatelabs/karate">GitHub</a>.</p><h3>Automating Integration Tests</h3><p>In today’s fast-paced tech world, speed and high quality are a must. Manual testing can slow down progress and does not guarantee a bug-free release. Automating integration tests speeds up the process, allowing developers to focus on creating great features instead of checking for bugs. Running tests automatically after every code change ensures that issues are found and dealt with quickly. This leads to faster and well tested releases without any regression. Regular automated testing means your software stays reliable as new features are added. If something breaks, you catch it early before it impacts users. To do this, in this article, we will use Karate for writing tests and GitLab CI to automate running tests.</p><h4>What is GitLab CI ?</h4><p>Now, let’s talk about GitLab CI. This is a powerful tool for Continuous Integration (CI). It helps automate the process from code commit to deployment, much like a well-oiled machine. Once you set up GitLab CI, it can run your tests every time you make changes. Think of it as having a safety net — whenever you take a leap, it checks if everything is okay before you land.</p><h3>Sample Project (Hands on)</h3><h4>Typical Project Structure</h4><p>When integrating Karate into a real-world application, the structure of your test setup can vary based on your requirements and how you want to manage testing. Broadly, there are different common approaches:</p><ol><li><strong>Including Karate Tests in Your Microservice</strong><br>In this approach, Karate tests are added directly to your microservice project. This is particularly useful for testing the service in isolation, as you can <strong>mock external dependencies</strong> (e.g., external APIs or databases) to create controlled test environments. This approach keeps the test and application code in the same repository, simplifying version control and CI/CD workflows. Since the tests are tightly coupled with the microservice, the team responsible for the development and maintenance of the service is also responsible for writing and maintaining its integration tests. This approach works well when the focus is on validating the behavior of a single microservice, possibly with mocked external dependencies.</li><li><strong>Dedicated Karate Project for Integration Testing</strong><br>In this approach, you create a separate project (or module) dedicated to integration tests. This is ideal when you want to test the <strong>complete integration of your microservice with its external dependencies</strong> in a real-world-like setup. The tests in this project cover the interactions and integrations between multiple microservices or the system as a whole. The central QA or testing team are responsible for maintaining the project to ensure consistency across services and to manage cross-service scenarios effectively. This approach is ideal for end-to-end testing, ensuring that the entire system works as expected when multiple microservices interact.</li></ol><p>For the purposes of this article, we’ll focus on the second approach — a <strong>dedicated Karate project for integration testing</strong> in order to centralize the integration test code in the same project to test all our future microservices.</p><h4>Test Structure</h4><p>To simplify the example, we’ll omit creating a custom microservice and instead use the <strong>PetStore API </strong>(full documentation available <a href="https://editor.swagger.io">here</a>). This allows us to focus on the testing techniques and features of Karate without the overhead of setting up and managing a backend service.</p><p>Karate tests consist of *.feature files, where each file can define one or more scenarios. For the Pet store API, we might structure our test scenarios around major resources like Pet, Store, and User.</p><h4>Setting Up Karate with Maven</h4><p>First, set up Karate in a Maven project by adding dependencies in the pom.xml:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;com.intuit.karate&lt;/groupId&gt;<br>    &lt;artifactId&gt;karate-junit5&lt;/artifactId&gt;<br>    &lt;version&gt;1.4.1&lt;/version&gt; &lt;!--latest version when writing this article--&gt;<br>    &lt;scope&gt;test&lt;/scope&gt;<br>&lt;/dependency&gt;</pre><p>You can find the complete project on <a href="https://github.com/rayenrejeb/pet-store-api-karate-example">GitHub 🚀</a></p><p><strong>1. Testing the Pet Resource</strong></p><p>A test scenario to add a new pet might look like this:</p><pre>Feature: Petstore Pet API<br><br>Scenario: Add a new pet to the store<br>    Given url &#39;https://petstore.swagger.io/v2/pet&#39;<br>    And request { &quot;id&quot;: 123, &quot;name&quot;: &quot;Fluffy&quot;, &quot;photoUrls&quot;: [&quot;https://example.com/photo.jpg&quot;], &quot;status&quot;: &quot;available&quot; }<br>    When method post<br>    Then status 200<br>    And match response.name == &#39;Fluffy&#39;</pre><p><strong>2. Testing the Pet Resource</strong></p><p>This scenario tests placing an order:</p><pre>Scenario: Place an order for a pet<br>    Given url &#39;https://petstore.swagger.io/v2/store/order&#39;<br>    And request { &quot;id&quot;: 1, &quot;petId&quot;: 123, &quot;quantity&quot;: 2, &quot;status&quot;: &quot;placed&quot; }<br>    When method post<br>    Then status 200<br>    And match response.status == &#39;placed&#39;</pre><p><strong>3. Testing the User Resource</strong></p><p>A test scenario for creating a user:</p><pre>Scenario: Create a new user<br>    Given url &#39;https://petstore.swagger.io/v2/user&#39;<br>    And request { &quot;id&quot;: 101, &quot;username&quot;: &quot;testUser&quot;, &quot;firstName&quot;: &quot;John&quot;, &quot;lastName&quot;: &quot;Doe&quot;, &quot;email&quot;: &quot;john.doe@example.com&quot; }<br>    When method post<br>    Then status 200</pre><p><strong>4. Fetching a Pet by ID</strong></p><p>This scenario uses a parameterized path for retrieving a pet by ID:</p><pre>Scenario: Find pet by ID<br>    Given url &#39;https://petstore.swagger.io/v2/pet/123&#39;<br>    When method get<br>    Then status 200<br>    And match response.name == &#39;Fluffy&#39;</pre><h4><strong>Advanced Features in Karate</strong></h4><ul><li><strong>Parameterized Tests:</strong> To run tests with different values, use the Examples keyword. (<a href="https://github.com/karatelabs/karate/blob/master/karate-demo/src/test/java/demo/outline/examples.feature">example</a>)</li><li><strong>Data-Driven Tests:</strong> Use data tables in Karate to send multiple test data variations through a scenario. (<a href="https://github.com/karatelabs/karate/blob/master/karate-demo/src/test/java/demo/outline/examples.feature">example</a>)</li><li><strong>Reusability:</strong> Define reusable code in Background or as * def functions to be used across scenarios.</li></ul><p><strong>5. Another example for a scenario to test in Karate</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/602aa91760225371ba2c888629879b0d/href">https://medium.com/media/602aa91760225371ba2c888629879b0d/href</a></iframe><p>When we run that PetManagement.feature, we will have a karate-report for the results (available also in jsonformat)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/932/1*Quft3DTyQ2d-mSp_IVeJOw.png" /></figure><p>In order to run those tests on feature merge, we need to configure our .gitlab-ci.yml in our project.</p><h4>Configure GitLab CI</h4><p>To trigger a GitLab CI/CD pipeline when a merge request is merged into the develop branch, you can set up a .gitlab-ci.yml file with specific jobs to run tests. Here’s a sample pipeline configuration to execute whenever a merge request is merged</p><pre>stages:<br>  - test<br><br>trigger_karate_tests:<br>  stage: test<br>  only:<br>    - merge_requests<br>    - develop  # or other branches you want to run the tests on<br>  script:<br>    - echo &quot;Triggering Karate test pipeline...&quot;<br>    - |<br>      curl -X POST \<br>        -F token=$KARATE_TRIGGER_TOKEN \<br>        -F ref=main \<br>        -F variables[ENVIRONMENT]=$TARGET_ENV \<br>        https://gitlab.com/api/v4/projects/YOUR_KARATE_PROJECT_ID/trigger/pipeline<br>  # Wait for the triggered pipeline to finish before proceeding (optional)<br>  allow_failure: false</pre><p>TARGET_ENV<em>: </em>To specify which environment to test. This can be part of the URL of the service to be tested.</p><p>KARATE_TRIGGER_TOKEN is a sensitive value and should be protected. To secure this value in GitLab, you should store it as a <strong>protected CI/CD variable</strong> in GitLab. This way, it won’t be exposed in the pipeline logs or GitLab configuration files, and it will be accessible only to authorized branches or users. Here’s how to securely configure the KARATE_TRIGGER_TOKEN:</p><p><strong>Add </strong><strong>KARATE_TRIGGER_TOKEN as a CI/CD Variable</strong></p><ol><li>Go to your main project’s <strong>Settings &gt; CI/CD &gt; Variables</strong> section.</li><li>Click <strong>Add variable</strong>.</li><li>Enter KARATE_TRIGGER_TOKEN as the <strong>Key</strong>.</li><li>Paste the trigger token you generated in the Karate project as the <strong>Value</strong>.</li><li>Enable <strong>Mask</strong> to hide the token in job logs (this prevents accidental logging of sensitive information).</li><li>Enable <strong>Protect</strong> to restrict the token to protected branches (e.g., main, develop) and prevent it from being used in unprotected pipelines or feature branches.</li></ol><p>While this approach focuses on running integration tests after deploying a new artifact, a more robust strategy involves testing the artifact <strong>before</strong> deployment. In this approach, we would first build the new artifact, then run the integration tests on it. Only if the tests pass would we proceed with deploying the artifact to production. This ensures that the new artifact is validated early in the process, minimizing the risk of deploying faulty code and improving the reliability of the release pipeline. This strategy provides an additional layer of confidence before introducing changes to the live environment.</p><h3>Summary</h3><p>Automated integration testing is an essential practice for modern software development, helping teams ensure that components interact smoothly and that new features do not introduce regressions. By combining Karate, a powerful BDD framework, with GitLab CI/CD, teams can automate the process of running comprehensive integration tests every time a change is made. This approach reduces the risk of errors, improves efficiency, and ensures higher quality and stability of the software in production.</p><p>Karate simplifies API testing with its easy-to-write scenarios and robust feature set, including support for data-driven tests, parallel execution, and seamless integration with CI tools like GitLab. With Karate, developers can write tests that are both human-readable and executable, ensuring that their systems behave as expected. Furthermore, using GitLab CI allows teams to automate the execution of these tests on every commit or merge, ensuring that issues are caught early in the development cycle.</p><p>By following the practices outlined in this article, teams can streamline their integration testing process, achieve faster feedback, and ultimately build more reliable software. With the integration of Karate and GitLab CI, developers can confidently automate integration tests, making the process faster, more consistent, and scalable while maintaining the integrity of their applications.</p><p>Link: <a href="https://github.com/rayenrejeb/pet-store-api-karate-example">Karate GitHub Project</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a740131f11b1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/automated-integration-testing-using-karate-gitlab-ci-a740131f11b1">Automated Integration testing — using Karate &amp; GitLab CI</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Makers Paris 2025 — Jour 1]]></title>
            <link>https://medium.com/bforbank-tech/android-makers-paris-2025-jour-1-0cf27c177759?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/0cf27c177759</guid>
            <category><![CDATA[androidmakers]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Florian MONBEIG]]></dc:creator>
            <pubDate>Fri, 25 Apr 2025 13:39:57 GMT</pubDate>
            <atom:updated>2025-04-25T13:39:57.150Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8wdoOXlVkK8uBBr3PPh06w.png" /></figure><h3>Android Makers Paris 2025 — Jour 1</h3><p>Comme chaque année, j&#39;ai eu la chance d&#39;assister à la nouvelle édition d&#39;Android Makers dans les beaux locaux du beffroi de Montrouge et je vous invite à revivre avec moi cette journée riche en découverte. Entre modularité, sécurité, refonte d&#39;application et deep dive dans les entrailles de Compose puis de comment fonctionne les Regex, nous partons ensemble faire le tour de ce programme chargé.</p><p><strong>1. Ultra fast Build et Architecture Modulaire</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3pakrdmL9ok9jyZtQfjQOQ.jpeg" /></figure><p>La journée a démarré sur les chapeaux de roues avec la présentation de Cyril Mottier un développeur d’amo, qui nous a plongé dans les arcanes de l’architecture modulaire. Leur défi ? Optimiser les temps de build, un enjeu crucial dans le développement d’applications complexes, d&#39;autant plus quand on est dans un secteur ultra concurrentiel et toujours en mouvement des applications de réseaux sociaux. Il a partagé leur approche, articulée autour d’une modularisation poussée du projet. En divisant l’application en modules indépendants et grâce à une compréhension fine de l&#39;algorithme de compilation, ils ont réussi à réduire significativement les temps de build et à améliorer l&#39;efficacité de leur équipe. Cette présentation a souligné l’importance d’une bonne architecture, non seulement pour la maintenabilité du code, mais aussi pour la productivité des développeurs. J’ai pu observer que la modularisation, bien qu’elle représente un investissement initial parfois conséquent comme c&#39;est le cas ici, est une stratégie payante sur le long terme.</p><p><strong>2. Refonte et Design System : L’Odyssée de Coyote</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ow81aDoXvTJZeea5EmIOCw.jpeg" /></figure><p>Coyote, ce n’est pas qu’une application d’avertisseur pour les conducteurs, c’est aussi une entreprise qui a su innover dans son approche de refonte de son application. La deuxième conférence, animée par l’équipe de Coyote, nous a offert une plongée captivante dans le monde de la refonte d’application. Leur approche a été guidée par la mise en place d’un design system unifié, capable de servir leurs trois applications (iOS / Android / device Coyote). Le plus intéressant réside dans leur gestion des variations : chaque composant peut exister en versions Small ou Large, pour s’adapter aux différents contextes d’utilisation. Coyote a également partagé son organisation de la gestion de l’interface utilisateur, chaque Composable majeur ayant son propre UiState et ViewModel. Cela assure un contrôle fin de l’état de l’interface et une séparation claire des responsabilités facilitant les imports d&#39;une application a une autre. De plus, ils utilisent google screenshot testing pour s’assurer qu’il n’y aura pas de régressions. Une belle démonstration de la synergie entre dev et design dans le cadre d&#39;une refonte d&#39;application.</p><p><strong>3. Cybersécurité mobile : Les Menaces Cachées</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cMKEUog4esmZSjTjyQaShQ.jpeg" /></figure><p>La troisième conférence nous a fait prendre conscience des menaces qui rôdent dans le monde du mobile. Zimperium une société de sécurité mobile, nous a ouvert les yeux sur les vulnérabilités et les techniques d’attaque actuelles. On a été plongé dans le monde de la cybersécurité avec des exemples concrets :<br><strong>Magisk</strong> : Un plugin qui permet de rooter le téléphone et de le dissimuler aux applications, dont l&#39;utilisation a explosé ces dernières années.<br><strong>Frida</strong> : Un outil d’injection de code, un cauchemar pour la sécurité.<br>Un exemple assez fou a été donné, une application malveillante qui imite toutes les applications bancaires en Europe. L’utilisateur lance son application bancaire et en fait il s’agit de l’application malveillante qui se lance à la place reproduisant à la perfection la UI de la banque afin de récupérer les virements de l&#39;utilisateur à son profit.<br>Cette session a été un véritable électrochoc, nous rappelant que la sécurité ne doit jamais être prise à la légère. Il est crucial pour les développeurs d’intégrer des pratiques de sécurité rigoureuses dès les premières étapes du développement.</p><p><strong>4. Debuguer les Recompositions dans Android Studio : Les coulisses des devs d&#39;Android Studio.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-D93lYd9rGITo9oTvKPxEQ.jpeg" /></figure><p>Les équipes d’Android Studio sont venus sur scène pour nous dévoiler des outils et astuces de debugage sur les recompositions. Cette conférence fut technique, nous avons pu voir qu’il est possible d’avoir des logs sur le degré de stabilité des paramètres lors des recompositions grâce aux breakpoints (Stable / Unstable). Une autre fonctionnalité intéressante, c’est la possibilité de faire un break point et de lui configurer un &quot;println&quot; qui s&#39;affichera sur le terminal (fini les log en dur dans le code). On peut aussi injecter une valeur dans une variable en cours de debugage et de continuer à jouer notre code avec cette nouvelle valeur, idéale pour simuler des correctifs.</p><p>Aussi, si on souhaite avoir de meilleures performances avec notre device il est préférable d’utiliser un cable usb-3 ( attention usb-3 != usb-c) afin d&#39;avoir une meilleure vitesse d&#39;envoie des data. Un message de notification sera fait dans Android Studio si ce n’est pas le cas.</p><p><strong>5. Regex : L’Odyssée Intergalactique de Ben Kadel</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nbuxQHdIzfDoOde1BaTWqw.jpeg" /></figure><p>La dernière conférence était la plus surprenante et théâtrale de la journée. Comme a son habitude Ben Kadel nous a embarqués dans une aventure bien a lui et celle là était intergalactique avec l&#39;exploration du monde des Regex. Imaginez, un vaisseau spatial qui s’écrase sur la planète Regex, et notre périple commence à travers les sombres mystères des expressions régulière tout essayant de déjouer les plans de son alter ego maléfique Ken Badel. En tout cas, il a toujours une manière atypique et divertissante de capter notre attention pour nous apprendre des sujets complexes. C’était agréable de terminer par cette conférence pour clôturer cette riche journée.</p><p><strong>Conclusion : Une journée riche en apprentissages.</strong></p><p>Android Makers 2025 a été bien plus qu’une simple série de conférences. C’était une véritable immersion dans les enjeux et les innovations du développement Android. De l’optimisation des builds à la cybersécurité, en passant par la refonte d’application et les subtilités de Compose, chaque présentation a apporté son lot de connaissances et de réflexions. Bien sûr je n&#39;ai pas pu parler des autres sujets qui ont été abordé comme l&#39;émergence de l&#39;IA dans le développement, les réflexions organisationnelles sur le mobileOps et les applications multiplateforme en KMP, ni des chaleureuses conversations que j&#39;ai pu avoir avec mes confrères. Je vous en parlerais surement plus lors d&#39;une prochaine édition, dont je suis sur sera encore mieux que la précédente.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-kpprG8NX3iTWJcF1AwC9g.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0cf27c177759" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/android-makers-paris-2025-jour-1-0cf27c177759">Android Makers Paris 2025 — Jour 1</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Integrate Datadog into Your Android Application for Logging and Dynamic Sampling]]></title>
            <link>https://medium.com/bforbank-tech/how-to-integrate-datadog-into-your-android-application-for-logging-and-dynamic-sampling-48bc9948ef38?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/48bc9948ef38</guid>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[datadog]]></category>
            <dc:creator><![CDATA[Kais Jaafoura]]></dc:creator>
            <pubDate>Tue, 21 Jan 2025 14:10:32 GMT</pubDate>
            <atom:updated>2025-01-21T14:10:31.974Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xmo6OgKm7iXmb9p0RHYABg.png" /><figcaption>Datadog logo</figcaption></figure><h3>Introduction</h3><p>Datadog provides a comprehensive solution for monitoring and observability in modern applications. In this article, we’ll guide you through integrating Datadog into your Android application, setting up logging, and configuring dynamic session sampling with Firebase Remote Config.</p><h3>1. Setting Up Datadog in Your Android Application</h3><p>Before integrating Datadog, ensure you have a Datadog account with an API key. Then follow these steps:</p><h4>Step 1: Add Dependencies</h4><p>Update your build.gradle file to include the Datadog SDK:</p><pre>dependencies {<br>    implementation &quot;com.datadoghq:dd-sdk-android:1.+&quot;<br>    implementation &quot;com.google.firebase:firebase-config:21.2.1&quot; // Firebase Remote Config<br>}<br><br></pre><h4>Step 2: Initialize Datadog</h4><p>Initialize Datadog in your Application class :</p><pre>import com.datadog.android.Datadog<br>import com.datadog.android.DatadogConfig<br><br>class MyApplication : Application() {<br>    override fun onCreate() {<br>        super.onCreate()<br><br>        // Basic configuration<br>        val config = DatadogConfig.Builder(&quot;&lt;CLIENT_TOKEN&gt;&quot;, &quot;&lt;ENVIRONMENT&gt;&quot;)<br>            .build()<br><br>        // Initialize Datadog<br>        Datadog.initialize(this, config)<br>    }<br>}<br><br></pre><p>Replace &lt;CLIENT_TOKEN&gt; and &lt;ENVIRONMENT&gt; with your Datadog client token and the environment name (e.g., production, staging).</p><h4>Step 3: Grant Required Permissions</h4><p>Ensure your AndroidManifest.xml contains the required permissions:</p><pre>&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;<br>&lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot; /&gt;</pre><h3>2. Integrating Logging with Datadog</h3><h4>Step 1: Create a Logger</h4><p>Use the Logger API to start capturing logs:</p><pre>import com.datadog.android.log.Logger<br><br>val logger = Logger.Builder()<br>    .setNetworkInfoEnabled(true) // Attach network information<br>    .setLogcatLogsEnabled(true) // Forward logs to Logcat<br>    .build()<br><br></pre><h4>Step 2: Log Events</h4><p>Capture log messages of various levels:</p><pre>logger.i(&quot;Info message: App started&quot;)<br>logger.w(&quot;Warning message: Low memory&quot;)<br>logger.e(&quot;Error message: Network failure&quot;)</pre><p>You can also add custom metadata:</p><pre>logger.d(&quot;Debug message&quot;, attributes = mapOf(&quot;user_id&quot; to &quot;12345&quot;))</pre><h3>3. Dynamic Session Sampling with Firebase Remote Config</h3><p>Session sampling limits the percentage of logs sent to Datadog, helping you manage quota usage. Using Firebase Remote Config, you can dynamically adjust the sample rate without deploying new versions of your app.</p><h4>Step 1: Set Up Firebase Remote Config</h4><ol><li>Add Firebase to your Android project.</li><li>In the Firebase Console, define a parameter datadog_sample_rate with a default value (e.g., 100.0).</li></ol><h4>Step 2: Fetch the Sample Rate</h4><p>Retrieve and apply the sample rate during Datadog initialization:</p><pre>import com.datadog.android.DatadogConfig<br>import com.google.firebase.remoteconfig.FirebaseRemoteConfig<br><br>class MyApplication : Application() {<br>    override fun onCreate() {<br>        super.onCreate()<br><br>        val remoteConfig = FirebaseRemoteConfig.getInstance()<br>        remoteConfig.setDefaultsAsync(mapOf(&quot;datadog_sample_rate&quot; to 100.0))<br>        remoteConfig.fetchAndActivate().addOnCompleteListener { task -&gt;<br>            if (task.isSuccessful) {<br>                val sampleRate = remoteConfig.getDouble(&quot;datadog_sample_rate&quot;).toFloat()<br><br>                val config = DatadogConfig.Builder(&quot;&lt;CLIENT_TOKEN&gt;&quot;, &quot;&lt;ENVIRONMENT&gt;&quot;)<br>                    .setSampleRate(sampleRate) // Dynamic sample rate<br>                    .build()<br><br>                Datadog.initialize(this, config)<br>            }<br>        }<br>    }<br>}<br><br></pre><h4>Step 3: Update Sampling Rate Remotely</h4><p>Use the Firebase Console to update the datadog_sample_rate value. The new rate will be applied after the next fetch interval (default is 12 hours, configurable via Firebase).</p><h3>4. Best Practices</h3><ul><li><strong>Use Tags:</strong> Attach tags (e.g., userId, deviceType) for better log searchability.</li><li><strong>Avoid Sensitive Data:</strong> Exclude personal or sensitive data from logs.</li><li><strong>Leverage Log Levels:</strong> Use appropriate log levels (DEBUG, INFO, ERROR) to prioritize issues.</li></ul><h4>Sampling Strategies</h4><ul><li><strong>Monitor Usage:</strong> Use Datadog’s quota dashboard to track log volume.</li><li><strong>Start High, Then Optimize:</strong> Begin with a high sample rate (e.g., 100%) during development and gradually lower it in production.</li><li><strong>Dynamic Updates:</strong> Regularly adjust the sample rate using Firebase Remote Config based on real-time monitoring.</li></ul><h3>Conclusion</h3><p>By integrating Datadog for logging and leveraging Firebase Remote Config for dynamic session sampling, you can efficiently monitor your app’s performance while staying within log quotas. These tools provide flexibility and control, ensuring that your application remains stable and well-observed.</p><p>For more details, refer to the <a href="https://docs.datadoghq.com/fr/logs/log_collection/android/?tab=kotlin">official documentation</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=48bc9948ef38" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/how-to-integrate-datadog-into-your-android-application-for-logging-and-dynamic-sampling-48bc9948ef38">How to Integrate Datadog into Your Android Application for Logging and Dynamic Sampling</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Java and Kafka Adventure : The road to data consistency with OutBox Pattern]]></title>
            <link>https://medium.com/bforbank-tech/a-java-and-kafka-adventure-the-road-to-data-consistency-with-outbox-pattern-78e280752493?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/78e280752493</guid>
            <category><![CDATA[outbox-pattern]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[transactions]]></category>
            <dc:creator><![CDATA[LE Long]]></dc:creator>
            <pubDate>Thu, 16 Jan 2025 09:41:06 GMT</pubDate>
            <atom:updated>2025-01-16T09:41:06.478Z</atom:updated>
            <content:encoded><![CDATA[<h3>A Java and Kafka Adventure : The road to data consistency with OutBox Pattern</h3><p>As software engineer, one of the popular problems we deal with is ensuring data consistency. In Event-Driven Architecture (EDA), it is mandatory; in other words, your system goes wrong when the data exchange (publish/consume) is not coherent.</p><p>In EDA when using Kafka (or any other message brokers like RabbitMQ) to exchange messages and having a database to store the business data model. There are two different types of operations : Database transaction and kafka message publication.</p><p><strong><em>Database Transaction: </em></strong>This involves operations that ensure data integrity and consistency within a single database. It is managed by the database management system (DBMS) and follows ACID (Atomicity, Consistency, Isolation, Durability) properties.</p><p><strong><em>Kafka message publication</em>: </strong>The capability to push an event to a message broker. The capability to use a transaction also exists, but only to ensure atomicity between an event consumption and an event publication.</p><p><strong>-&gt; </strong>This involves coordinating transactions across multiple systems, such as a database and a message broker like Kafka. Ensuring atomicity and consistency across these systems is challenging due to the lack of a global transaction manager and the inherent differences in how these systems handle transactions.</p><h3><strong>“Enough theory, let’s dive into the problem”</strong></h3><p><em>(In this article, I’ll describe a fake production story with a very simple Java demo. This should be understandable for both technical and non-technical readers)</em></p><p>Example of a first tentative to ensure data consistency and transactional aspect:</p><pre>@Transactional    <br>public User createUser(User user) {<br>        // Persist user to the database<br>        User savedUser = userRepository.save(user);<br><br>        // Push message to Kafka<br>        kafkaTemplate.send(TOPIC, savedUser);<br><br>        return savedUser;<br>    }</pre><blockquote>At this time, I believed that will work well and it’s quite a great and simple implementation…<strong> until I was called to help investigating issues in Production.</strong></blockquote><p>There were about 600 users created in our store but we didn’t receive the notification for 4 users.</p><p>As I trusted my implementation, <strong><em>I wonder how it was possible to happen?</em></strong></p><p>By talking with my colleagues, I figured out that something can go wrong between the step of writing to the database and publishing the message. The system becomes inconsistent.</p><blockquote>Key issues: Atomic transaction</blockquote><p><strong>Atomicity</strong> (all-or-nothing) between database operations and message publishing is not guaranteed -&gt; it leads to inconsistent data.</p><p>If we choose to fix the consequences and not the issue’s root cause, it takes a tremendous amount of time to find the corrupted data and patch it manually.</p><p>This is just a simple example and may not usually happen in real life, but in a complex system, this is one of the pain points in scaling Event-Driven Architecture.</p><h3><strong>The solution : Outbox pattern</strong></h3><p>If we can not have a single transaction operating kafka and database to ensure atomicity ,<strong> then we should handle them independently</strong>. It is the principle of the outbox pattern that i have learned from my colleagues and chapter engineering. It’s called Transactional Outbox Pattern.</p><p>Firstly, we need to create another table, such as “outbox”, to store the state of the message to be published. We persist the state changes of the business data and the “event data” using an atomic operation. Both succeed together or fail together.</p><p>Secondly, we trigger the “event data” based on status by using a scheduled job to retrieve all the committed messages and ready for asynchronous publishing</p><p><em>Regarding the diagram:</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RMydMV6sVMKq68uM_epc2g.png" /><figcaption><strong>Outbox Pattern Diagram</strong></figcaption></figure><p><em>So let’s jump into the refactor code !</em></p><p><strong><em>Outbox </em>event<em> and business use case :</em></strong></p><pre>public enum OutboxEvent {<br>  USER_CREATION,  // event type for user creation<br>  USER_UPDATED,   // event type for user updated<br>  CARD_CREATION  //  event type for card creation<br>}<br>public enum OutboxStatus {<br>  PENDING, // state of pending<br>  FAILED, //  state of failed<br>  SENT   //   state of sent<br>}</pre><pre>public class OutboxEvent {<br>    private UUID id; // unique identifier<br>   <br>    private UUID businessId; // business id linked to business data<br>    <br>    private OutboxType eventType; // outbox event type<br>    <br>    private OutboxStatus status; // outbox status<br><br>    private LocalDateTime lastModifiedTime; // last modified<br><br>  public OutboxEvent(UUID businessId, OutboxType eventType, OutboxStatus status) {<br>    this.id = null;<br>    this.businessId = businessId;<br>    this.eventType = eventType;<br>    this.status = status;<br>    lastModifiedTime = LocalDateTime.now();<br>  }<br>// constructor, getter setter<br>}</pre><pre>@Transactional(propagation = Propagation.REQUIRED)<br>    public User createUser(User user) {<br>        // Persist user to the database<br>        User savedUser = userRepository.save(user);<br><br>        // Create outbox event<br>        OutboxEvent outboxEvent = new OutboxEvent(<br>                                        savedUser.id(), <br>                                        OutboxType.USER_CREATION,<br>                                        OutboxStatus.PENDING<br>                                   );<br>        outboxEventRepository.save(outboxEvent);<br>        return savedUser;<br>    }</pre><blockquote>p/s: propagation = Propagation.REQUIRED -&gt; support a current transaction; create a new one if none exists.</blockquote><p>Here, I created an outbox model to store the state of event data. We can have the business ID which is linked to the business data model, the status of the outbox like PENDING, FAILED, SENT, etc., or outboxType as I want to handle many event types.</p><p>Now in my use case, I don’t have any more interaction with Kafka or any distributed transaction. It focuses only on data persistence.</p><p>Then, i have a scheduling job to publish event data :</p><pre>// EnableScheduling<br><br>    public void publishEvents() {<br>        List&lt;OutboxEvent&gt; events = outboxEventRepository.findByPendingEvents();<br>        for (OutboxEvent event : events) {<br>            // userWrappedService convert the outbox event to message event<br>            var message = userWrappedService.toMessage(event)<br>            kafkaTemplate.send(TOPIC, message);<br>            event.setStatus(OutboxStatus.SENT);<br>            event.setLastModifiedTime(LocalDateTime.now());<br>            outboxEventRepository.save(event);<br>        }<br>    }</pre><p><em>p/s: we can improve it by:</em></p><ul><li><em>Sending multiple records in a single batch to Kafka</em></li><li><em>Implementing idempotent processes</em></li><li><em>Improving the process of retry</em></li></ul><p>Here, I would like to ensure the message is published correctly. I don’t need to worry about business data as it was guaranteed in my previous use case.</p><p>I want to improve this code to ensure the message is sent successfully before marking the event as sent. I collect events that are successfully sent and use a batch update to reduce the number of database calls.</p><p>Here is the refactored code :</p><pre>// EnableScheduling<br>public void publishEvents() {<br>        List&lt;OutboxEvent&gt; events = outboxEventRepository.findByPendingEvents();<br>        List&lt;OutboxEvent&gt; successfullySentEvents = new ArrayList&lt;&gt;();<br><br>        for (OutboxEvent event : events) {<br>            var message = userWrappedService.toMessage(event);<br>            ListenableFuture&lt;SendResult&lt;String, Message&gt;&gt; future = kafkaTemplate.send(TOPIC, message);<br><br>            future.addCallback(new ListenableFutureCallback&lt;&gt;() {<br>                @Override<br>                public void onSuccess(SendResult&lt;String, Message&gt; result) {<br>                    event.setStatus(OutboxStatus.SENT);<br>                    event.setLastModifiedTime(LocalDateTime.now());<br>                    successfullySentEvents.add(event);<br>                }<br><br>                @Override<br>                public void onFailure(Throwable ex) {<br>                    log.error(&quot;Failed to send event: {}&quot;, event.getId(), ex);<br>                }<br>            });<br>        }<br><br>        if (!successfullySentEvents.isEmpty()) {<br>            outboxEventRepository.saveAll(successfullySentEvents);<br>        }<br>    }</pre><p>Finally, I have refactored my implementation to prevent distributed transactions issues. After this refactor, I observed that <strong>the issues previously faced in Production no longer occur.</strong></p><h3>Summary</h3><p>Understanding distributed transactions and implement Outbox pattern is not an easy thing when you face this problem for the first time. I believe it’s one of the best solutions when dealing with consistency issues in an Event Driven Architecture.</p><p>The goal: have the guarantee that the database and message publication will be synchronized (eventual consistency). If it makes your system more resilient, why wouldn’t you try it ?</p><p><em>In my article, i would like to thank you @</em><a href="https://fr.linkedin.com/in/zied-abderrahim-7566bb54"><em>ABDERRAHIM Zied </em></a><em>for supporting and helping me work effectively with the outbox pattern. Together, we made the system of order card at @BforBank more resilient.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78e280752493" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/a-java-and-kafka-adventure-the-road-to-data-consistency-with-outbox-pattern-78e280752493">A Java and Kafka Adventure : The road to data consistency with OutBox Pattern</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kafka tips & tricks #01: Static field and static value with Kafka Connect]]></title>
            <link>https://medium.com/bforbank-tech/kafka-tips-tricks-01-static-field-and-static-value-with-kafka-connect-9156667f7fa9?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/9156667f7fa9</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[kafka-connect]]></category>
            <category><![CDATA[tech]]></category>
            <dc:creator><![CDATA[Tonihasina Rabeharisoa]]></dc:creator>
            <pubDate>Tue, 14 Jan 2025 13:12:22 GMT</pubDate>
            <atom:updated>2025-01-14T13:12:22.417Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3u32xaln_vG31MzqSjjjfg.jpeg" /></figure><p>Kafka Connect is a very powerful tool for streaming data between Apache Kafka to another system. Often you may need to include static fields in your data or to set a field to a static value. This article explains how to do so via Kafka connect with practical examples.</p><h3>How to ?</h3><p>Within Kafka Connect, Single Message Transform (SMT) helps you to make simple and lightweight modifications to individual messages.</p><h3>Use case 1: Adding a static field</h3><p>In this example, we will add a field that does not exist in the original record. To do so we will use SMT InsertField</p><p>We will insert a static field labeled origin using a static value of Earth.</p><pre>&quot;transforms&quot;: &quot;InsertField&quot;,<br>&quot;transforms.InsertField.type&quot;: &quot;org.apache.kafka.connect.transforms.InsertField$Value&quot;,<br>&quot;transforms.InsertField.static.field&quot;: &quot;origin&quot;,<br>&quot;transforms.InsertField.static.value&quot;: &quot;Earth&quot;</pre><h4>Original record:</h4><pre>{<br> &quot;firstName&quot;: &quot;Marty&quot;, <br> &quot;lastName&quot;: &quot;McFly&quot;<br>}</pre><h4>Transformed record:</h4><pre>{<br> &quot;firstName&quot;: &quot;Marty&quot;, <br> &quot;lastName&quot;: &quot;McFly&quot;, <br> &quot;origin&quot;: &quot;Earth&quot;<br>}</pre><h3>Use case 2: Masking a field</h3><p>In this example, we will mask some fields. To do so we will use SMT MaskField.</p><p>We will perform two transformations:</p><ul><li>First one we will mask the field cardNumber to a masked value ***-***-****</li><li>Second one will will mask two fields office and mobile to a masked value +xx-xxx-xxxxxxx</li></ul><pre>&quot;transforms&quot;: &quot;CardNumberMask,PhoneMask&quot;,<br><br>&quot;transforms.CardNumberMask.type&quot;: &quot;org.apache.kafka.connect.transforms.MaskField$Value&quot;,<br>&quot;transforms.CardNumberMask.fields&quot;: &quot;cardNumber&quot;,<br>&quot;transforms.CardNumberMask.replacement&quot;: &quot;***-***-****&quot;,<br><br>&quot;transforms.PhoneMask.type&quot;: &quot;org.apache.kafka.connect.transforms.MaskField$Value&quot;,<br>&quot;transforms.PhoneMask.fields&quot;: &quot;office,mobile&quot;,<br>&quot;transforms.PhoneMask.replacement&quot;: &quot;+xx-xxx-xxxxxxx&quot;</pre><h4>Original record:</h4><pre>{<br> &quot;cardNumber&quot;: &quot;1230-5460-9000-0000&quot;, <br> &quot;office&quot;: &quot;+33145310884&quot;,<br> &quot;mobile&quot;: &quot;+33766250585&quot;<br>}</pre><h4>Transformed record:</h4><pre>{<br> &quot;cardNumber&quot;: &quot;***-***-****&quot;, <br> &quot;office&quot;: &quot;+xx-xxx-xxxxxxx&quot;,<br> &quot;mobile&quot;: &quot;+xx-xxx-xxxxxxx&quot;<br>}</pre><h3>Use case 3: Inserting a field or setting static value to a field that might already exist</h3><p>In the two previous uses cases, we either have inserted a field that does not exist in the original record or have modified an existing field in the original record. What if the field <strong>may exist or not </strong>and we want to set a static value ?</p><ul><li>Using only InsertField will cause an error if the field already exists because you cannot insert an already existing field</li><li>Using MaskField will not do the job if the field does not exist, as MaskField will not add it.</li></ul><p>The trick to complete that is to mix ReplaceField and InsertField</p><p>In this example, we will set a static field labeled origin using a static value of Earth. But, the field might already exists or not. So, first, we will rename the field viaReplaceField , then we will insert the target field with target value via InsertField.</p><pre>&quot;transforms&quot;: &quot;RenameOrigin, InsertOrigin&quot;,<br>&quot;transforms.RenameOrigin.type&quot;: &quot;org.apache.kafka.connect.transforms.ReplaceField$Value&quot;,<br>&quot;transforms.RenameOrigin.renames&quot;: &quot;origin:defaultOrigin&quot;<br>&quot;transforms.InsertOrigin.type&quot;: &quot;org.apache.kafka.connect.transforms.InsertField$Value&quot;,<br>&quot;transforms.InsertOrigin.static.field&quot;: &quot;origin&quot;,<br>&quot;transforms.InsertOrigin.static.value&quot;: &quot;Earth&quot;</pre><h4>Original record:</h4><pre>{<br> &quot;firstName&quot;: &quot;Marty&quot;, <br> &quot;lastName&quot;: &quot;McFly&quot;<br> &quot;origin&quot;: &quot;Mars&quot; // Might not exist in the original record<br>}</pre><h4>Transformed record:</h4><pre>{<br> &quot;firstName&quot;: &quot;Marty&quot;, <br> &quot;lastName&quot;: &quot;McFly&quot;,<br> &quot;defaultOrigin&quot;: &quot;Mars&quot; // Not present in the transformed if not present in original record<br> &quot;origin&quot;: &quot;Earth&quot;<br>}</pre><h3>Conclusion</h3><p>In this article, we have seen the usage of Single Message Transform (SMT) on top of Kafka Connect to make simple and lightweight modifications to individual messages. We have seen how to insert a static field or mask an existing field or how to handle it if the field that we want to insert might already exist or not and we want to set a static value.</p><p>To go further: <a href="https://docs.confluent.io/platform/current/connect/transforms/overview.html">https://docs.confluent.io/platform/current/connect/transforms/overview.html</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9156667f7fa9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/kafka-tips-tricks-01-static-field-and-static-value-with-kafka-connect-9156667f7fa9">Kafka tips &amp; tricks #01: Static field and static value with Kafka Connect</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scripting with Swift]]></title>
            <link>https://medium.com/bforbank-tech/scripting-with-swift-d4ee4ebe97a9?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/d4ee4ebe97a9</guid>
            <category><![CDATA[tooling]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[scripting]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Bacem BEN AFIA]]></dc:creator>
            <pubDate>Tue, 14 Jan 2025 07:41:34 GMT</pubDate>
            <atom:updated>2025-01-14T10:41:42.306Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/896/1*2Za3uwdevQT2GGIIW8TvFw@2x.jpeg" /><figcaption>Tooling with Swift</figcaption></figure><p>Imagine you’re giving someone directions to your house. You wouldn’t just say, “Get here.” You’d describe each turn and landmark along the way. That’s what’s a script for a computer!</p><p>A script, is a set of instructions written in a programming language. It tells a computer what to do, step by step to achieve a specific mission.</p><p>Scripts could perform a variety of tasks such as:</p><ul><li>automate process,</li><li>data entry and file manipulation,</li><li>backup and restore operations,</li><li>communicate between homogenous or heterogenous systems…</li></ul><p><strong>Note:</strong> Scripting in this context means “tooling”; that small code we write to achieve a task, not taking in consideration the nature of the used language wether it was a compiled, interpreted or a hybrid language<em>.</em></p><blockquote>« Basically, scripting is not a technical term. When we call something a scripting language, we’re primarily making a linguistic and cultural judgment, not a technical judgment » <a href="https://www.perl.com/pub/2007/12/06/soto-11.html/#author-bio-larry-wall">Larry Wall</a> — <a href="https://www.perl.com/pub/2007/12/06/soto-11.html/">Perl.com</a></blockquote><p>Usually when talking about scripts, the first reflex that cames in mind (not for python users) is to create a new .sh file, also called “Shell Script” file.<br>Shell script is good ! until nobody in your team will remember what it was created for and how it should run.</p><p>As a software developer, specifically Apple Software developer, I can say i’m convinced that in a team were all player ‘speaks’ a common language like “Swift” it would be better to use it not only for developing, but also for tooling.</p><blockquote>An Apple Software developer might forget the Shell Script syntaxe for a loop or a condition, but never in swift.</blockquote><p>Maintaining scripts over the time won’t require re-explaining each instruction since it is naturally readable for Apple Software developers.</p><h3>Hello World !</h3><p>Let’s start simple, a script that prints Hello World !<br>Create a file <em>script.sh </em>and paste in it:</p><pre>#!/bin/sh<br>echo &quot;hello, world!&quot;</pre><p>Then add to the current session user the right to execute it, then run it:</p><pre>chmod +x script.sh <br>./script.sh</pre><p>The result should be “hello, world!” printed in terminal. Easy !</p><p>In order to receive arguments passed to the script during its call, also easy.<br>let’s update the <em>script.sh</em> file:</p><pre>#!/bin/sh<br>echo &quot;hello $1&quot;</pre><p>Then call it with arguments:</p><pre>./script.sh John</pre><p>The result should be “hello John” printed in terminal. Yet easy !</p><p>We can also use interruption to request parameter from the user and read it within the script:</p><pre>#!/bin/sh<br>echo &quot;Whom to greet ?&quot;<br>read fullname<br>echo &quot;hello $fullname&quot;</pre><p>Run it</p><pre>./script.sh<br><br>&gt; Whom to greet ? <br>&gt; John<br>&gt; hello John</pre><p>A piece of cake, until I tell you I want, for exemple, a script that add a text to an image ! ooh yeah it’s getting complicated.<br>To do so with shell script we need to install 3rd party solution such as ImageMagick or others.<br>But as Apple Software developer we know that combining a text and an image ain’t that complicated for Swift !</p><h3>Hello World, the Swift way !</h3><p>First of all, we need to check if we already have Swift toolchain installed:</p><pre>swift -version<br>&gt; swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)<br>&gt; Target: arm64-apple-macosx14.0</pre><p>If its not see installation page for <a href="https://www.swift.org/install">Xcode or Swift Toolchain</a>, it depends on the platform to be used on.</p><p>To start, create a file <em>Script.swift </em>and paste in it:</p><pre>import Foundation<br><br>print(&quot;hello, world!&quot;)</pre><p>save and run:</p><pre>swift Script.swift</pre><p>Same result as the first shell script !</p><p>Note that we can add the <strong>shebang (</strong>#!<strong>) </strong>and<strong> the path to swift binary </strong>at the beginning of the swift file and run it without specifying the interpreter “swift”:</p><pre>#!/usr/bin/swift<br>import Foundation<br><br>print(&quot;hello, world!&quot;)</pre><p>and run it this way:</p><pre>chmod +x Script.swift <br>./Script.swift</pre><p>Let’s try to read argument passed during script call. We need to update our script to be like:</p><pre>import Foundation<br><br>print(&quot;hello \(CommandLine.arguments[1])&quot;)</pre><p>Call it with the argument:</p><pre>swift Script.swift John</pre><p>We should have the same result as the Shell script “hello John”</p><p>Moving on to reading from standard input example, another update to our script and it should be like:</p><pre>import Foundation<br><br>print(&quot;Whom to greet ?&quot;)<br>var name = readLine(strippingNewline: true)<br>print(&quot;hello \(name ?? &quot;&quot;)&quot;)</pre><p>At this level there is no big difference between Shell and Swift scripts.</p><p><strong>Why Swift script can be better alternative to a shell script ? </strong>Remember<strong> </strong>the first line of the Swift script;</p><pre>import Foundation</pre><p>We are able to use the full potential of Foundation Framework or Any other framework or library we will include within our script.</p><p>Here is a demo sample where we need to execute some setup depending on the input value of a configuration name:</p><pre>import Foundation<br><br>enum Configuration: String, CaseIterable {<br>    case DEBUG<br>    case QUALIF<br>    case PPROD<br>    case PROD<br>}<br><br>// Demo setup func<br>func setup(with configuration: Configuration) {<br>    print(configuration.rawValue)<br>}<br><br>let input = CommandLine.arguments[1]<br>// Check if this CFG Name is Supported or Not <br>guard let configuration = Configuration(rawValue: input) else {<br>    // CFG Name not Supported<br>    print(&quot;can&#39;t find configuration for name \(input)&quot;)<br>    print(&quot;available configurations are:&quot;)<br>    Configuration.allCases.forEach {<br>        print($0.rawValue)<br>    }<br>    exit(1)<br>}<br>// Perform specifique action for CFG<br>switch configuration {<br>case .DEBUG, .QUALIF, .PPROD, .PROD:<br>    setup(with: configuration)<br>}</pre><p>Now let’s see how to do the merge between a text and and image. Given the input is BforBank Logo image:</p><figure><img alt="BforBank Logo." src="https://cdn-images-1.medium.com/max/1024/1*9CJwaeYGHey28aUvmyba_A.png" /><figcaption>BforBank Logo</figcaption></figure><pre>import AppKit<br><br>extension NSImage {<br>    convenience init? (url: URL) {<br>        guard let iconData  = try? Data (contentsOf: url) else {<br>            return nil<br>        }<br>        self.init(data: iconData)<br>    }<br>    var asPNGData: Data? {<br>        guard let imageData = tiffRepresentation, let newRep = NSBitmapImageRep(data: imageData) else {<br>            return nil<br>        }<br>        return newRep.representation(using: .png, properties: [:])<br>    }<br>}<br><br>func addBadge(_ text: String, imageSize: CGSize, with color: NSColor) {<br>    let paragraphStyle = NSMutableParagraphStyle()<br>    paragraphStyle.alignment = NSTextAlignment.center<br>    paragraphStyle.lineBreakMode = .byWordWrapping<br><br>    let attributes: [NSAttributedString.Key: Any] = [<br>        .font: NSFont.systemFont(ofSize: 120),<br>        .paragraphStyle: paragraphStyle,<br>        .foregroundColor: color<br>    ]<br><br>    let textRect = NSRect(x: 0, y: imageSize.height * 3/4, width: imageSize.width, height: imageSize.height * 1/4)<br>    <br>    NSColor(red: 0.78, green: 0.25, blue: 0.28, alpha: 1.00)<br>        .drawSwatch(in: textRect)<br>    <br>    text.draw(<br>        with: textRect.insetBy(dx: 20, dy: 60),<br>        options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], attributes: attributes)<br>}<br><br>// Get the original Icon file url<br>let iconUrl = URL(filePath: FileManager.default.currentDirectoryPath + &quot;/Icon.png&quot;)<br><br>// Build NSimage from Icon file url<br>guard let iconImage = NSImage(url: iconUrl) else {<br>    print(&quot;Error: couldn&#39;t find Icon.png&quot;)<br>    exit (1)<br>}<br><br>// Read Badge Text from standard input<br>print(&quot;Badge Text:&quot;)<br>var badgeText = readLine(strippingNewline: true)<br><br>// Apply text on badge<br>let badgedIcon = NSImage(size: iconImage.size, flipped: true) { rect -&gt; Bool in<br>    iconImage.draw(in: rect)<br>    addBadge(badgeText ?? &quot;&quot;, imageSize: iconImage.size, with: .white)<br>    return true<br>}<br><br>// Generate png Data from the combined image<br>guard let pngData = badgedIcon.asPNGData else {<br>    print(&quot;Error: couldn&#39;t convert image to png data&quot;)<br>    exit (1)<br>}<br><br>// Generate new file with the png Data<br>let outputURL = URL(filePath: FileManager.default.currentDirectoryPath + &quot;/BadgedIcon.png&quot;)<br>try pngData.write (to: outputURL)</pre><pre>&gt; swift Script.swift<br>&gt; Badge Text:<br>&gt; Alpha</pre><p>“et Voila”:</p><figure><img alt="BforBank Logo with text equal to “Alpha” as a badge" src="https://cdn-images-1.medium.com/max/1024/1*14zudoYUyNvN5UmuuSLlrA.png" /></figure><p>This was a drop in an ocean, with swift we can:</p><ul><li>automate templating and code generation directly from apis documentation, check (<a href="https://stencil.fuller.li/en/latest/">Stencils</a>)</li><li>access system process just like shell script (<a href="https://developer.apple.com/documentation/foundation/process/">Process</a>)</li><li>benefit from Swift existing advantages ( Rich frameworks, Structured concurrency, Macro, Property wrappers, SPM …)</li></ul><p>Finally for All Swift developers;</p><blockquote>“It feels at home” Natan Rolnik — <a href="https://async.techconnection.io/frenchkit">SwiftConnection 2024</a></blockquote><p><a href="https://async.techconnection.io/talks/swift-connection/swift-connection-2024/natan-rolnik-mastering-swift-for-scripting-and-tooling/">Here</a> we can find a good talk at <a href="https://async.techconnection.io/frenchkit">SwiftConnection 2024</a> where we can see further subjects such as the usages of <a href="https://stencil.fuller.li/en/latest/">Stencil</a> for templating and automated generation, Macros ...</p><p>A <a href="https://rderik.com/blog/using-swift-for-scripting/">good article</a> that tackle more the usage of Process.</p><p>And a random Swift subject to read as a bonus <a href="https://www.swift.org/documentation/lldb/">“Swift REPL” Read-Eval-Print-Loop</a> 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4ee4ebe97a9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/scripting-with-swift-d4ee4ebe97a9">Scripting with Swift</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Paging 3 through clean architecture]]></title>
            <link>https://medium.com/bforbank-tech/paging-3-through-clean-architecture-4d771ab19fa8?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/4d771ab19fa8</guid>
            <dc:creator><![CDATA[Florian MONBEIG]]></dc:creator>
            <pubDate>Wed, 04 Dec 2024 13:33:49 GMT</pubDate>
            <atom:updated>2024-12-04T13:33:49.357Z</atom:updated>
            <content:encoded><![CDATA[<p>I recently had the opportunity to implement Paging 3 within a clean, multi-module architecture for an Android project using Kotlin and Compose. I wanted to share my approach with you.</p><p>We’ll explore the different layers of clean architecture (Data, Domain, and Presentation) and demonstrate how to implement each step. However, we won’t cover implementing a RemoteMediator, as my project doesn’t utilize an internal database.</p><p>For my example, I’ve chosen the theme of cheese because… well, I’m French.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/1*4fN32H5VJOgF71MP4ngM6g.png" /></figure><p><strong>1 — Imports and Clean architecture</strong></p><p>First, let’s discuss about imports. Since we’re using clean architecture, our Domain layer is unaware of the other layers of the application and that also mean it should be composed of Kotlin dependencies only.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/1*d314E-3HJbglW9jOZQhC2g.png" /></figure><p>You might be thinking this presents a problem. Paging 3 is an Android library, so we can’t integrate it directly into the Domain layer… Don’t worry, Google has anticipated this! There are versions of Paging 3 without Android dependencies, like the one used for testing :</p><pre>dependencies {<br>  val paging_version = &quot;3.3.4&quot;<br><br>//DATA<br>  implementation(&quot;androidx.paging:paging-runtime:$paging_version&quot;)<br><br>//DOMAIN<br>  // alternatively - without Android dependencies for tests<br>  testImplementation(&quot;androidx.paging:paging-common:$paging_version&quot;)<br><br>  // optional - RxJava2 support<br>  implementation(&quot;androidx.paging:paging-rxjava2:$paging_version&quot;)<br><br>  // optional - RxJava3 support<br>  implementation(&quot;androidx.paging:paging-rxjava3:$paging_version&quot;)<br><br>  // optional - Guava ListenableFuture support<br>  implementation(&quot;androidx.paging:paging-guava:$paging_version&quot;)<br><br>//PRESENTATION<br>  // optional - Jetpack Compose integration<br>  implementation(&quot;androidx.paging:paging-compose:3.3.4&quot;)<br>}</pre><p>You can then integrate the appropriate dependency into each of your layers, depending on whether you need the Android-dependent version or not.</p><p><strong>2 — Implementing the Data Source</strong></p><p>We’ll begin on the data layer by implementing PagingSource. This class allows us to manage how we retrieve and store responses from our API for Paging.</p><pre>class CheesePagingSource @Inject constructor(//Hilt for injection<br>  private val apiRequestManager: ApiRequester,//A class handling all my api calls<br>  private val cheeseService: CheeseService, <br>  private val limit: Int,            //the number of cheese fetch by api<br>) : PagingSource&lt;String, CheeseItem&gt;() {<br><br>  override suspend fun load(params: LoadParams&lt;String&gt;): LoadResult&lt;String, CheeseItem&gt; {<br>    val response = apiRequestManager.request(<br>      service = &quot;CHEESE_SERVICE&quot;,<br>      call = {<br>        cheeseService.getCheeseItems(<br>          cursor = params.key,<br>          limit = limit<br>        )<br>      },<br>      converter = { it.toEntity() }<br>    )<br>    return onLoadResponse(response)<br>  }<br><br>  override fun getRefreshKey(state: PagingState&lt;String, CheeseItem&gt;): String? {<br>    return null<br>  }<br><br>  private fun onLoadResponse(<br>    response: Result&lt;EveryCheeseItem&gt;<br>  ): LoadResult&lt;String, CheeseItem&gt; {<br>    return when (response) {<br>      is Result.Success -&gt; {<br>        val cheeseList = response.data.cheeses<br>        LoadResult.Page(<br>          data = cheeseList,<br>          prevKey = null,<br>          nextKey = response.data.cursor<br>        )<br>      }<br>      is Result.Failure -&gt; {<br>        LoadResult.Error(response.error.throwable ?: Throwable())<br>      }<br>    }<br>  }<br>}</pre><p>The load function’s purpose here is to return a LoadResult object containing the data from our API call. I preferred to separate the API call from the part that handles the Result type (Success or Failure), to be more readable.</p><p>What’s most important to us is the returned LoadResult object. Here, we’ll send it our cheeseList objects, already transformed into Entities for is use in the Domain layer.</p><p>Regarding keys (prevKey / NextKey / getRefreshKey), I’m using a cursor instead of traditional integer-based pagination. That’s why prevKey and getRefreshKey is always null, as everything resides on the same page.</p><pre>is Result.Success -&gt; {<br>  val cheeseList = response.data.cheeses<br>  LoadResult.Page(<br>    data = cheeseList,<br>    prevKey = null,<br>    nextKey = response.data.cursor<br>  )<br>}</pre><p><strong>3 — Implementing the Repository</strong></p><p>Our PagingSource will be utilized in our remote Repository, as it’s part of creating our Flow with the Pager object.</p><pre>class CheeseRemoteRepository @Inject constructor(<br>  private val apiRequestManager: ApiRequester,<br>  private val cheeseService: CheeseService<br>) : CheeseRepository {<br><br>  override suspend fun getCheeseItems(limit: Int): Flow&lt;PagingData&lt;CheeseItem&gt;&gt; {<br>    return Pager(<br>      config = PagingConfig(pageSize = limit),<br>      pagingSourceFactory = {<br>        CheesePagingSource(<br>          apiRequestManager = apiRequestManager,<br>          cheeseService = cheeseService,<br>          limit = limit<br>        )<br>      }<br>    ).flow<br>  }<br>}</pre><p>Here, we simply create the Pager object, passing it a PagingConfig and our PagingSource to create a flow of PagingData&lt;Value&gt;. You can think of PagingData as a wrapper around your values that simplifies interaction within the presentation layer.</p><p><strong>4 — The Use Case</strong></p><p>This can be a quick step depending on your implementation. Here, the use case will simply act as a bridge between the Data and Presentation layers.</p><pre>class GetCheeseItemsUseCase @Inject constructor(<br>  private val cheeseRepository: CheeseRepository,<br>) {<br>  suspend fun execute(size: Int): Flow&lt;PagingData&lt;CheeseItem&gt;&gt; =<br>    cheeseRepository.getCheeseItems(limit = size)<br><br>}</pre><p><strong>5 —The ViewModel</strong></p><p>Now, we enter the ViewModel. This is where we’ll declare our state and retrieve our PagingData flow to apply transformations. Typically, we want to map our entity model from Domain to a UI model representing our UI, which is entirely possible using the map extension function available for PagingData. However, be aware that PagingData doesn’t have all the extensions that Flow offers.</p><p>The second change we want to make is using the <strong>.insertSeparators</strong> method provided by PagingData. This allows us to add elements directly into our flow like a header, a divider and so on. To achieve this, I recommend creating a sealed class with all the element types you want to display in your UI.</p><pre>sealed class CheeseDisplayItem{<br>  data object NoCheeseDisplayAvailable : CheeseDisplayItem()<br>  data object NoMoreCheeseDisplay : CheeseDisplayItem()<br>  data object CheeseDisplayListTitle : CheeseDisplayItem()<br>  data class CheeseItem(val cheeseItemUiModel : CheeseItemUiModel) : CheeseDisplayItem()<br>}</pre><pre>private val cheeseState: MutableStateFlow&lt;PagingData&lt;CheeseDisplayItem&gt;&gt; = MutableStateFlow(value = PagingData.empty())<br>val state: MutableStateFlow&lt;PagingData&lt;CheeseDisplayItem&gt;&gt; get() = cheeseState<br><br>private fun getCheeseItems() {<br>  viewModelScope.launch {<br>    getCheeseItemsUseCase.execute(<br>      size = 15,<br>    ).distinctUntilChanged()<br>      .map { pagingData -&gt;<br>        pagingData<br>          .map {<br>            it.toUiModel()   // transform your entity model to uiModel if needed<br>          }<br>          .insertSeparators { before, after -&gt;<br>            when {<br>              before == null &amp;&amp; after == null -&gt; CheeseDisplayItem.NoCheeseDisplayAvailable   //no item was found<br>              before == null &amp;&amp; after != null -&gt; CheeseDisplayItem.CheeseDisplayListTitle     // first item on the list<br>              after == null -&gt; CheeseDisplayItem.NoMoreCheeseDisplay                          // last item on the list<br>              else -&gt; null                                                        // always put null for the other case<br>            }<br>          }<br>      }<br>      .cachedIn(viewModelScope)<br>      .collect {<br>        cheeseState.value = it<br>      }<br>  }<br>}</pre><p><strong>6 — Displaying Data in the Screen</strong></p><p>The first step is to retrieve your state and create a LazyPagingItems instance. You can then display the different types from your sealed class in your UI.</p><p>One of the key features of Paging is that you can manage your loading states with much greater precision, as you can see in the example below :</p><pre>val cheeseItems: LazyPagingItems&lt;CheeseDisplayItem&gt; = viewModel.timelinePaging.collectAsLazyPagingItems()<br><br>LazyColumn(modifier = Modifier.padding(horizontal = BFBTheme.spacing.spacing16)) {<br>  //display items depending on its type<br>  items(count = cheeseItems.itemCount) { index -&gt;<br>    when (val cheeseItem = cheeseItems[index]) {<br>      CheeseDisplayItem.CheeseDisplayListTitle -&gt; CheeseTitle()<br>      CheeseDisplayItem.NoCheeseDisplayAvailable -&gt; NoCheeseFound()<br>      CheeseDisplayItem.NoMoreCheeseDisplay -&gt; NoMoreCheese()<br>      is CheeseDisplayItem.CheeseItem -&gt; Cheese(cheeseItem.cheeseItemUiModel)<br>      else -&gt; Unit<br>    }<br>  }<br>  // Manage different type of loading state thanks to Paging<br>  cheeseItems.apply {<br>    when {<br>      loadState.refresh is LoadState.Loading -&gt; {<br>        item { Text(text = &quot;LOADING&quot;) }<br>      }<br><br>      loadState.refresh is LoadState.Error -&gt; {<br>        item { Text(text = &quot;Error when refresh&quot;) }<br>      }<br><br>      loadState.append is LoadState.Error -&gt; {<br>        item {<br>          Text(text = &quot;Error when append&quot;)<br>        }<br>      }<br>    }<br>  }<br>}</pre><p>There you have it! I hope this has shed some light on implementing Paging3. Don’t hesitate to leverage its advantages to optimize your display by configuring the Pager’s PagingConfig, utilizing insertSeparators, and taking advantage of the simplified loading state management.</p><p>GL HF</p><p>For more information</p><p><a href="https://developer.android.com/topic/libraries/architecture/paging/v3-overview">https://developer.android.com/topic/libraries/architecture/paging/v3-overview</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d771ab19fa8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/paging-3-through-clean-architecture-4d771ab19fa8">Paging 3 through clean architecture</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to patch entirely a Kafka Streams state store ?]]></title>
            <link>https://medium.com/bforbank-tech/how-to-patch-entirely-a-kafka-streams-state-store-3cc731410308?source=rss----13f907e6a3b---4</link>
            <guid isPermaLink="false">https://medium.com/p/3cc731410308</guid>
            <category><![CDATA[data-migration]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[kafka-streams]]></category>
            <category><![CDATA[bforbank]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Maroinemlis]]></dc:creator>
            <pubDate>Tue, 05 Nov 2024 15:37:10 GMT</pubDate>
            <atom:updated>2024-11-05T22:44:48.163Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to patch entirely a Kafka Streams State Store ?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/333/1*eHFcyVpjZaCa8rUhoe9g6w.png" /></figure><h3><strong>Introduction</strong></h3><p>Kafka Streams is a powerful framework for building real-time, event-driven applications, allowing data processing in near real-time with scalability. One of its core components is the <strong>State Store</strong>, which holds intermediate results and makes operations like aggregations, joins, and windowing possible.</p><p>However, there may come a time when the state in these stores requires modification. This might be necessary to resolve data inconsistencies, accommodate schema changes, or fix bugs in the state. In this guide, we’ll explore an effective approach to patch Kafka Streams State Stores.</p><h3>What is a Kafka Streams State Store?</h3><p>A <strong>State Store</strong> in Kafka Streams is a local, persistent storage system where data is saved and accessed during stream processing. It is especially critical for maintaining the state of aggregations, joins, and windowed operations.</p><p>There are three common types of State Stores:</p><ul><li><strong>KeyValueStore</strong>: Stores key-value pairs and is ideal for lookups and simple updates.</li><li><strong>WindowStore</strong>: Holds windowed data, allowing access to records within specific time windows.</li><li><strong>SessionStore</strong>: Maintains session data, useful for session-based activities that require tracking start and end times.</li></ul><p>To ensure fault tolerance, Kafka Streams can back up this data to Kafka topics (called <em>changelog topics</em>). In case of a failure, the state store can be restored from the changelog. When you need to modify this data for patching, however, the process becomes more complex.</p><h3>Why Patch the Kafka Streams State Store?</h3><p>Patching a state store isn’t something you do on a whim. It’s typically required when there’s a problem or significant change in the system. Here are the main scenarios where patching becomes necessary:</p><ul><li><strong>Data Corruption</strong>: Over time, the data in the state store may become inconsistent or corrupted, whether due to bugs or external factors. You’ll need to patch the store to restore data integrity.</li><li><strong>Schema Changes</strong>: If your application’s data schema evolves (e.g., new fields, changing data types), the existing state may no longer be valid. Patching the state store ensures it aligns with the new schema.</li><li><strong>Business Logic Updates</strong>: If the business logic for computing state (such as how you calculate aggregates or process joins) changes, the historical state data may need to be updated to reflect those changes.</li><li><strong>Bug Fixes</strong>: Sometimes, incorrect logic or bugs can lead to faulty states in the store. In such cases, you must patch the store to fix the erroneous data.</li><li><strong>Data Migration</strong>: When migrating data between systems or Kafka Streams instances, you might need to update or completely overwrite the state store’s data with new values.</li></ul><p>While patching is necessary in some cases, it can also be risky. Improper handling of the patching process can lead to data loss, application crashes, or inconsistent states across different nodes in a distributed Kafka Streams application.</p><h3>Exemple of a KeyValueStore patching</h3><p>Let’s walk through a specific use case of patching a KeyValueStore&lt;String, String&gt;. Our goal is to update entries in the store where (key+i, value) becomes (key+i, valuepatched) for entries where i % 2 == 0.</p><p>Here’s the core Processor implementation used to achieve this update</p><pre>package com.kafka.stream.migration.demo.process.process;<br><br>import org.apache.kafka.streams.processor.PunctuationType;<br>import org.apache.kafka.streams.processor.api.ContextualProcessor;<br>import org.apache.kafka.streams.processor.api.ProcessorContext;<br>import org.apache.kafka.streams.processor.api.Record;<br>import org.apache.kafka.streams.state.KeyValueStore;<br><br>import java.time.Duration;<br><br>public class MyMigrationProcess extends ContextualProcessor&lt;Object, Object, String, String&gt; {<br><br>    private final String storeName;<br>    private final Duration punctuateIntervalDuration;<br>    private boolean isPunctuatedOnce;<br><br>    private KeyValueStore&lt;String, String&gt; store;<br><br>    public MyMigrationProcess(String storeName, Duration punctuateIntervalDuration) {<br>        this.storeName = storeName;<br>        this.punctuateIntervalDuration = punctuateIntervalDuration;<br>        this.isPunctuatedOnce = false;<br>    }<br><br>    @Override<br>    public void init(ProcessorContext&lt;String, String&gt; processorContext) {<br>        super.init(processorContext);<br>        store = processorContext.getStateStore(storeName);<br>        processorContext.schedule(punctuateIntervalDuration, PunctuationType.WALL_CLOCK_TIME, this::punctuate);<br>    }<br><br>    private void punctuate(long clockTime) {<br>        if (isPunctuatedOnce) {<br>            return;<br>        }<br>        this.isPunctuatedOnce = true;<br>        var iterator = store.all();<br>        while (iterator.hasNext()) {<br>            var next = iterator.next();<br>            if (Integer.parseInt(next.key.replace(&quot;key&quot;, &quot;&quot;)) % 2 == 0) {<br>                String patchValue = patchValue(next.value);<br>                store.put(next.key, patchValue);<br>            }<br><br>        }<br>    }<br><br>    private String patchValue(String value) {<br>        return value + &quot;patched&quot;;<br>    }<br><br>    @Override<br>    public void process(Record&lt;Object, Object&gt; emptyRecord) {<br>        //Do nothing<br>    }<br><br>}<br><br></pre><ul><li><strong>One-Time Punctuation</strong>: The punctuate method ensures the patching logic runs only once. It iterates over the store entries and selectively modifies entries based on the condition (i % 2 == 0).</li><li><strong>State Store Iteration</strong>: store.all() is used to access and modify each entry in the store.</li></ul><h3>Building the Topology</h3><p>Because this patching operation doesn’t require an input topic (we’re simply walking through the state store to apply updates), we can bypass Kafka’s topic validation by creating a dummy stream. It serves only as a trigger for our custom processor and does not consume any real data. We achieve this by using a regular expression pattern that Kafka Streams interprets as a valid input, even though there are no matching topics.</p><pre>streamsBuilder<br>.stream(Pattern.compile(&quot;dummy-topic-juste-to-generate-stream&quot;))<br>.process(() -&gt; new MyMigrationProcess(STORE_NAME, Duration.ofSeconds(1)), STORE_NAME);<br>        </pre><ul><li><strong>Pattern.compile(&quot;dummy-topic-just-to-generate-stream&quot;)</strong>: This regular expression bypasses Kafka’s requirement for a real input topic, allowing the processor to run independently.</li><li><strong>Processor Execution</strong>: The custom processor (MyMigrationProcess) is attached directly to the stream, and it executes the patching logic solely on the state store.</li></ul><p>This workaround is effective for triggering processors when you don’t need actual streaming data, enabling focused modifications on the state store contents.</p><h3><strong>Testing using TopologyTestDriver</strong></h3><p>Using TopologyTestDriver is significantly faster than an EmbeddedSingleNodeKafkaCluster, and it allows for precise simulation of various timing scenarios. By calling advanceWallClockTime, you can simulate the progression of stream time to trigger scheduled callbacks like punctuation exactly when needed.</p><p>Keep in mind that TopologyTestDriver operates as a single-partition simulation, so when testing scenarios that involve multiple partitions, results may differ from a fully distributed Kafka environment.</p><pre>package com.kafka.stream.migration.demo.process;<br><br>import org.apache.kafka.common.serialization.Serdes;<br>import org.apache.kafka.streams.StreamsConfig;<br>import org.apache.kafka.streams.Topology;<br>import org.apache.kafka.streams.TopologyTestDriver;<br>import org.apache.kafka.streams.state.KeyValueStore;<br>import org.junit.jupiter.api.BeforeEach;<br>import org.junit.jupiter.api.Test;<br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.boot.autoconfigure.kafka.KafkaProperties;<br>import org.springframework.boot.test.context.SpringBootTest;<br>import org.springframework.kafka.test.context.EmbeddedKafka;<br>import org.springframework.test.context.ActiveProfiles;<br><br>import java.time.Duration;<br>import java.util.Properties;<br>import java.util.stream.Stream;<br><br>import static org.assertj.core.api.Assertions.assertThat;<br><br>@ActiveProfiles(&quot;test&quot;)<br>@SpringBootTest<br>class MyMigrationTest {<br><br>    @Autowired<br>    Topology topology;<br><br>    @Autowired<br>    Properties testProp;<br><br>    @Autowired<br>    KafkaProperties kafkaProperties;<br><br>    @Test<br>    void should_patch_state_store() {<br><br>        //Given<br>        testProp.putAll(kafkaProperties.getProperties());<br>        TopologyTestDriver topologyTestDriver = new TopologyTestDriver(topology, testProp);<br><br>        KeyValueStore&lt;String, String&gt; keyValueStore = topologyTestDriver.getKeyValueStore(&quot;my-state-store-name&quot;);<br>        var ids = Stream.iterate(0, i -&gt; i + 1).limit(10).toList();<br>        ids.forEach(i -&gt; keyValueStore.put(&quot;key&quot; + i, &quot;value&quot; + i));<br><br>        //When stream advanced by one second<br>        topologyTestDriver.advanceWallClockTime(Duration.ofSeconds(1));<br><br>        //Then only key mod 2 = 0 is patched<br>        ids.stream().filter(i -&gt; i % 2 == 0).forEach(i -&gt; assertThat(keyValueStore.get(&quot;key&quot; + i)).isEqualTo(&quot;value&quot; + i + &quot;patched&quot;));<br>        ids.stream().filter(i -&gt; i % 2 != 0).forEach(i -&gt; assertThat(keyValueStore.get(&quot;key&quot; + i)).isEqualTo(&quot;value&quot; + i));<br><br>    }<br><br>}</pre><h3>Conclusion</h3><p>In summary, patching a Kafka Streams State Store requires careful planning and execution to avoid disruptions. By implementing a custom Processor, as demonstrated in the example, you can iterate over and selectively modify data within the KeyValueStore. Using a one-time punctuation, this approach triggers the patching logic without needing an active input stream. Testing with TopologyTestDriver enables you to simulate time progression and validate the patching logic, ensuring reliable results before deployment.</p><p>This method provides a controlled and efficient way to apply updates to state stores, whether to fix data inconsistencies, adapt to schema changes, or adjust business logic all while minimizing risk to the ongoing streaming process.</p><h4><strong>Additional Resources</strong></h4><ul><li>Github demo link: <a href="https://github.com/maroinemlis/kafka-stream-migration-demo">https://github.com/maroinemlis/kafka-stream-migration-demo</a></li><li><a href="https://kafka.apache.org/documentation/streams/">Kafka Streams Documentation</a></li><li><a href="https://kafka.apache.org/documentation/streams/developer-guide/config-streams.html">Monitoring Kafka Streams State Stores</a></li><li><a href="https://www.confluent.io/fr-fr/blog/test-kafka-streams-with-topologytestdriver/">https://www.confluent.io/fr-fr/blog/test-kafka-streams-with-topologytestdriver/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3cc731410308" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bforbank-tech/how-to-patch-entirely-a-kafka-streams-state-store-3cc731410308">How to patch entirely a Kafka Streams state store ?</a> was originally published in <a href="https://medium.com/bforbank-tech">Bforbank Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>