<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Jan Galinski on Medium]]></title>
        <description><![CDATA[Stories by Jan Galinski on Medium]]></description>
        <link>https://medium.com/@jangalinski?source=rss-297171ba3cfd------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*tiGJr7iPMG0V7rhfCZGafw.png</url>
            <title>Stories by Jan Galinski on Medium</title>
            <link>https://medium.com/@jangalinski?source=rss-297171ba3cfd------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 03:37:55 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@jangalinski/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[camunda-platform-7-mockito has moved — new home: holunda-io/c7-mockito]]></title>
            <link>https://jangalinski.medium.com/camunda-platform-7-mockito-has-moved-new-home-holunda-io-c7-mockito-1bae4376a29a?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/1bae4376a29a</guid>
            <category><![CDATA[camunda]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[holisticon]]></category>
            <category><![CDATA[camunda7]]></category>
            <category><![CDATA[holunda]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Sat, 06 Dec 2025 09:59:17 GMT</pubDate>
            <atom:updated>2025-12-06T09:59:17.244Z</atom:updated>
            <content:encoded><![CDATA[<h3>camunda-platform-7-mockito has moved — new home: holunda-io/c7-mockito</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JKzgIhIgTGU1Pbq-etCD3A.png" /><figcaption>We are moving!</figcaption></figure><p>Quick heads-up to everyone using <a href="https://github.com/camunda-community-hub/camunda-platform-7-mockito"><strong>camunda-platform-7-mockito</strong></a> in your tests: We’ve <strong>archived</strong> the original repository in the Camunda Community Hub and <strong>moved active development to our new monorepo</strong>:</p><ul><li>Old (archived): <a href="https://github.com/camunda-community-hub/camunda-platform-7-mockito/releases/tag/7.24.2">camunda-community-hub/camunda-platform-7-mockito</a></li><li>New (active): <a href="https://www.holunda.io/c7/stable/user-guide/c7-mockito/index.html">holunda-io/c7-mockito</a></li></ul><h3>Why we’re doing this</h3><p>Camunda Platform 7 <strong>Community Edition (CE) reached End of Life with 7.24</strong>. That means there won’t be any functional development for CE anymore. Future Camunda 7 releases will be <strong>Enterprise Edition only</strong>.</p><p>We do not see any use in developing new features for a discontinued CE version. However, if you are an enterprise customer and use some of our camunda extensions, you will need new releases of the extensions as well, as we need to keep the code base compatible with new camunda releases and 3rd party versions.</p><p>The <strong>c7 monorepo is our “one stop shop”</strong> for these libraries going forward, so we can keep things consistent and reduce maintenance overhead.</p><h3>Action for your projects</h3><p>If you reference the old repo in docs or links, please update them to point to:</p><pre>&lt;groupId&gt;io.holunda.c7&lt;/groupId&gt;<br>&lt;artifactId&gt;c7-mockito&lt;/artifactId&gt;<br>&lt;version&gt;2025.12.1&lt;/version&gt;</pre><p>That’s all for now, we kept the code base unchanged, so you do not have to change anything. Please note that with upcoming 2026 releases, we wil have a breaking change due to changing packages and artifacts!</p><p>Thanks to everyone who’s been using and contributing to camunda-platform-7-mockito — see you in the new home. 👋</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1bae4376a29a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Where’s the Food?]]></title>
            <link>https://medium.com/holisticon-consultants/wheres-the-food-01267291e2fe?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/01267291e2fe</guid>
            <category><![CDATA[bpmn]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Mon, 13 Nov 2023 08:23:05 GMT</pubDate>
            <atom:updated>2023-11-13T08:23:05.093Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GOezCE0MjzNOBztKQ4Rqpg.png" /><figcaption>Source: <a href="https://openai.com/dall-e-2">Dall-E</a></figcaption></figure><p>I first unveiled this real-life story while kicking off my talk, “Meta Programming — Code Generation with Kotlin Poet,” at this year’s Code Talks conference. Since I’ve been retelling this tale in various discussions, I thought it’s high time to share it with a broader audience. So, gather around, grab a cup of coffee, and let’s dive in…</p><p><em>Earlier this year, at Holisticon, we hosted Girls and Boys Day. If you’re not familiar, this is an event in Hamburg where seventh graders visit their families, friends, and relatives at their workplaces to observe how adults spend their day and gain insights into the world of work. Given our expertise in process automation and natural language processing at </em><a href="https://www.holisticon.de/"><em>Holisticon</em></a><em>, we assigned them the task of implementing a process, or more precisely, this process:</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IwboTR6ghGV4BXMxRQBpTg.png" /><figcaption><em>Chat GPT-powered “What to Cook for Dinner” process as build by our kids.</em></figcaption></figure><p><em>So, here’s basically how this BPMN process works:</em></p><ul><li><em>you send an email informing us that you’re expecting three guests for dinner (e.g.: one is vegetarian, one doesn’t consume alcohol, and one prefers chicken)</em></li><li><em>Chat GPT then analyses your request, suggests a meal, provides a recipe, and even generates a grocery list for the items you need to shop.</em></li><li><em>The kids also explored letting GPT translate this into French or Spanish …</em></li></ul><p><em>While this is essentially a fun project, the underlying technology is astonishing. You can find more information about it </em><a href="https://github.com/holunda-io/camunda-8-connector-gpt"><em>here</em></a><em>.</em></p><p><em>The kids spent a few hours implementing this process, and it worked seamlessly. They could send an email and receive a response from Chat GPT, instructing them on what to cook, how to prepare it, and what to shop for. <br>We were all thrilled, and the kids were rightfully proud.</em></p><p><em>We were impressed: “Wow, this is incredible. What an exciting time to be alive!”</em></p><p><em>However, once I returned home, I couldn’t shake the thought: “</em><strong><em>Wait a minute</em></strong><em> — a bunch of 12-year-olds just constructed a functioning process application that serves its intended purpose, deployed it to the cloud, and executed it. <br></em><strong><em>This is what I do for a living!</em></strong><em> If children can achieve this today, what will happen in another week or year? My managers and clients might acquire this skill, and I’ll find myself unemployed and starving.”</em></p><p><em>So, that night, I went to bed feeling frustrated and anxious. Yet, when I awoke the next day, a different concern struck me:</em></p><h4><em>Wait a minute — I didn’t see any food.</em></h4><p><em>We were all impressed by the shiny emails, recipes, and shopping lists, but the crucial question remained:</em></p><ul><li><em>Is the food tasty?</em></li><li><em>Does it provide enough for three people?</em></li><li><em>Can we cook the meal with the items on the grocery list?</em></li><li><em>If I were to build a business around this concept, how many customers might I lose every month due to dissatisfaction with my service?</em></li></ul><p><em>I wholeheartedly agree that we must reduce manual coding. Charging three-digit hourly rates for tasks that any child could copy and paste from a chat page is no longer sustainable. This isn’t necessarily a bad thing; many of the boring, error-prone tasks we currently perform every day should be automated. (Yes, Liquibase, I’m talking to you!)</em></p><p><em>Nonetheless, it’s our responsibility to ensure that the entire </em><strong><em>software generation process becomes deterministic, reproducible, and scalable</em></strong><em>.</em></p><p>That’s where I left off in September. During my conference talk, I emphasized the importance of controlling the generative process, preferably by writing the generators ourselves (<a href="https://youtu.be/_qNCx1NqyVY?si=BU3P3eiaBwQL8toL">you can watch the video for more</a>).</p><h4>What now?</h4><p>Let me break it down in simpler terms: We’re entering a world where 2 plus 2 might not always equal 4 anymore. <br>The result may vary depending on what the customer wants, what they are willing to pay, and how we design the system. It also could be any value between 3.8 or 4.2.</p><p>We need to determine if this tolerance is acceptable, establish mechanisms to guarantee it never falls below 3.7, and empower our customers to make the necessary decisions.</p><p>This will not make us lose our jobs … this will lead to more work, more meaningful work and thus more fun.</p><p><strong>So we were right: What an exciting time to be alive!</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=01267291e2fe" width="1" height="1" alt=""><hr><p><a href="https://medium.com/holisticon-consultants/wheres-the-food-01267291e2fe">Where’s the Food?</a> was originally published in <a href="https://medium.com/holisticon-consultants">Holisticon Consultants</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Back to the Future — Camunda Task UI mediation done right]]></title>
            <link>https://medium.com/holisticon-consultants/back-to-the-future-camunda-task-ui-mediation-done-right-cf5084a4296d?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/cf5084a4296d</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[camunda]]></category>
            <category><![CDATA[bpmn]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Thu, 26 Oct 2023 22:24:12 GMT</pubDate>
            <atom:updated>2023-11-08T09:37:19.544Z</atom:updated>
            <content:encoded><![CDATA[<h3>Back to the Future — Camunda Task UI mediation done right</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PS9c2xbN1vWuPZw2FCz7VA.png" /><figcaption>The example process: start the process through a controller and return the user task form, although business logic is executed in between.</figcaption></figure><p>Ever-so-often when I build process applications for my customers projects, I am confronted with the same requirement: One the one hand, we want to model complex business logic flows as BPMN files and let camunda control the execution, including asynchronous continuations, external workers, event subscriptions and so on. <br>On the other hand, from the users perspective, the interaction should feel like a synchronous, wizard like screen flow.</p><p>For years, we implemented this behavior using polling and timeouts, as described in this almost 12 year old, but still relevant article: <a href="http://www.bpm-guide.de/2012/04/04/pageflow-vs-process-flow-and-ui-mediator-pattern/">“Page-Flow vs. Process-Flow — how a UI Mediator might help”</a>.</p><p>Though this is a working solution that proofed its value, there is quite a lot to consider and implement, both on the web client- and on the process application-side.</p><p>So there has to be a better solution. And there is: Register your controller for the desired process state <em>(in the simpliest case: the next user task is created) </em>and retrieve a CompletableFuture in return. Then use a delegate expression listener on an activity to complete the future, thus continuing the synchronous execution of the controller logic and return the desired result.</p><p>Take the above BPMN diagram: We want to start a process <em>(by clicking some button for example) </em>and directly start working on the user task, so it feels like we are just navigating through a SPA flow.</p><p>But: There is some expensive heavy business logic that has to be executed before the user task is ready to work on. This logic could (as mentioned above) also involve external task workers or message communication between distributed systems. In our simple case here, we just hid a timer that waits for 5 seconds inside a collapsed embedded sub process:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/682/1*jgUyh2QXbFqeU5kU5geJEQ.png" /><figcaption>Our “expensive long running business logic” hidden inside the sub process</figcaption></figure><p>Let’s have a look at the implementation in detail.</p><h4><strong>Step 1</strong></h4><p>Start a new process instance and register a CompletableFuture. Instead of a ProcessInstance or its Id , we return this future to the controller.</p><pre>private val futures = ConcurrentHashMap&lt;BusinessKey, CompletableFuture&lt;TaskId&gt;&gt;()<br><br>fun startProcess(businessKey: BusinessKey): CompletableFuture&lt;TaskId&gt; {<br>  val future = futures.computeIfAbsent(businessKey) { CompletableFuture() }<br>  runtimeService.startProcessInstanceByMessage(MSG_START, businessKey)<br>  return future<br>}</pre><p>We need a “registry” for the future, so the async process execution can access it later.</p><h4>Step 2</h4><p>Use a TaskListener on creation of the UserTask to complete the future:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XXeDsnI8jcE5BbSHTAQHgw.png" /><figcaption>Alternative TaskListener implementation: A field on the process backing bean, referenced via SPEL.</figcaption></figure><pre><br>val onTaskCreation = TaskListener {<br>  futures.remove(it.execution.businessKey)?.complete(it.id)<br>}</pre><p>So no matter how long the process needs to reach the state we are interested in, it will be able to look up the future on the registry, remove it (it has fulfilled its duty) and complete it.</p><h4>Step 3</h4><p>In the controller, keep the future and join() to block the controller thread while the process is executing. Then return the creation URI for the task form:</p><pre>@PostMapping<br>fun startProcess(@RequestParam businessKey: BusinessKey): ResponseEntity&lt;Unit&gt; {<br>  val taskId = process.startProcess(businessKey).join()<br>  return ResponseEntity<br>    .created(URI.create(&quot;/camunda/app/tasklist/default/#/?task=$taskId&quot;))<br>    .build()<br>}</pre><p>And voila: we are done:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DmRytw2FSHg4ZYs57mbh7w.png" /><figcaption>This worked as we hoped for: we get a `201 CREATED` with the location we need to open the task in the camuda tasklist</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HyevcW-eZUrveyFzzJSJ1g.png" /><figcaption>Our task in the camunda tasklist, just click: Complete and you are done.</figcaption></figure><h4>Summary</h4><p>We just saw a simple, effective way to block the controller until we reached a desired state in the process. There was no need to provide a query endpoint for the UserTask and implement a polling or retry loop in the frontend to continue the UI flow. The key element is the future registry, which allows the synchronous controller flow and the asynchronous process flow to share the same CompletableFuture instance and thus notify each other on when something is done.</p><h4>Discussion</h4><p>Two things quickly come to mind: First: this mediation relies on an in-memory registry. What if the application fails during execution of business logic, what if the system goes offline, what if we loose the registry or try to re-execute the same instance? <br>Well, these are always tricky scenarios that have to be discussed carefully, BUT: I strongly believe, that this future based UI mediation does not supply any additional risk here. <br>Either the business logic before the UserTask fails … then the target state is never reached and you will need to handle the incident, no traditional polling would help you to continue. <br>Or the business logic is successful, but something with registering/accessing the future went wrong, or you try to access a future that has already been completed … In this case, you still have a persisted defined state in the camunda database and can easily recover by just opening the task in your tasklist to continue.</p><p>In any case, it is of course a good idea to define a timeout on future completion, so you do not keep the client waiting forever. Or, even better, consider returning the future directly, so your UI client can wait for the completion. Even switching to webflux/SSE and just pushing the correct state to the client is possible without much additional coding, but that is out of the scope of this example.</p><p>As always, you can read the full example source code in my <a href="https://github.com/jangalinski/camunda-kotlin-example-application/blob/develop/src/main/kotlin/future/mediator-with-future.kt">Github-repository</a>, please feel free to play around and provide improvements, or share your thoughts and questions in the comment section.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cf5084a4296d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/holisticon-consultants/back-to-the-future-camunda-task-ui-mediation-done-right-cf5084a4296d">Back to the Future — Camunda Task UI mediation done right</a> was originally published in <a href="https://medium.com/holisticon-consultants">Holisticon Consultants</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using “a” history plugin with spring-boot and camunda platform 7 community edition]]></title>
            <link>https://medium.com/holisticon-consultants/using-the-history-plugin-with-spring-boot-and-camunda-platform-7-community-edition-835c1b87138?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/835c1b87138</guid>
            <category><![CDATA[camunda]]></category>
            <category><![CDATA[plugins]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Fri, 04 Nov 2022 17:36:04 GMT</pubDate>
            <atom:updated>2024-03-13T11:18:26.589Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>TL;DR:</strong> If you just want to see it running, scroll down to <em>Installation in camunda spring boot</em>.</p><p>I assume that if you read this, you are familiar with the <a href="https://docs.camunda.org/manual/latest/user-guide/spring-boot-integration/">camunda platform 7 spring boot</a> solution. And maybe you are running the community (aka FOSS) edition of this wonderful process engine. <br>Could be because you are just exploring for a pet project, you are still evaluating and have not yet switched to the enterprise edition (which I would still recommend for production usage) or are brave and run your production environment without enterprise support … <br>Either way, one thing you might possibly be missing is the history plugin. Once your process instance finished it is no longer visible in the <a href="https://docs.camunda.org/manual/latest/webapps/cockpit/">camunda cockpit</a> and you have to use the REST or Java API of the history-service to find out what exactly happened during execution.</p><p>I recently learned about a community cockpit plugin while chatting at this years CamundaCon in Berlin and finally got the time to explore it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*goJKkKji63sKr3bBM_pQIw.gif" /><figcaption>screencast made by datakurre (<a href="https://github.com/datakurre/camunda-cockpit-plugins/">https://github.com/datakurre/camunda-cockpit-plugins/</a>)</figcaption></figure><h3>Open source “history plugin” for Camunda Cockpit</h3><p>The plugin has been created by Camunda Champion Asko Soukka (aka <em>datakurre</em>) and is available on his github repository. Thanks to the “new“ camunda cockpit system (introduced with camunda 7.14), it just consists of four <em>*.js</em> files that you have to provide to your camunda (spring-boot) application.</p><p>I grew up in a time when installing a custom plugin required downloading and unzipping the camunda-webapp war file, modifying its content, repackage it in the correct way and deploy it to a mysterious software component called the JBoss application server. So I was really impressed how easy this has become. Kudos to the camunda team.</p><p>Asko did a great job of documenting the plugin features (and how it is not supposed to replace the enterprise plugin nor claims to make an EE subscription obsolete) in his <a href="https://datakurre.pandala.org/2021/01/camunda-cockpit-history-plugin/">blog post</a> and the <a href="https://github.com/datakurre/camunda-cockpit-plugins/#readme">github readme</a>. <br>But using spring boot, I was missing some concrete hints how to install and use it in my camunda CE project. Luckily, he forwards to a <a href="https://forum.camunda.org/t/minimal-cockpit-history-plugins-for-camunda-7-14-0/24651">camunda forum discussion</a> where this problem has been solved.</p><h3>Installation in camunda spring boot</h3><p>To safe you some time, I will document my learnings here. It is really simple:</p><ol><li>Create a spring boot application and use the camunda spring boot starter.</li><li>create a the directory `./src/main/resources/META-INF/resources/webjars/camunda/app/cockpit/scripts`</li><li>Clone the <a href="https://github.com/datakurre/camunda-cockpit-plugins">repository</a> or just download the files mentioned in his docs</li><li>copy those files to the scripts repository</li><li>run your application</li></ol><p>Once you are done, it should look like this:</p><pre>src/main/resources/<br>├── META-INF<br>│   ├── resources<br>│   │   └── webjars<br>│   │       └── camunda<br>│   │           └── app<br>│   │               └── cockpit<br>│   │                   └── scripts<br>│   │                       ├── config.js<br>│   │                       ├── definition-historic-activities.js<br>│   │                       ├── instance-historic-activities.js<br>│   │                       ├── instance-route-history.js<br>│   │                       └── robot-module.js</pre><p>Now go to the process instance view of the cockpit and you can switch to the history tab and explore the activites and process variables of your completed instances that otherwise would not be available in the CE cockpit anymore. Nice work!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=835c1b87138" width="1" height="1" alt=""><hr><p><a href="https://medium.com/holisticon-consultants/using-the-history-plugin-with-spring-boot-and-camunda-platform-7-community-edition-835c1b87138">Using “a” history plugin with spring-boot and camunda platform 7 community edition</a> was originally published in <a href="https://medium.com/holisticon-consultants">Holisticon Consultants</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Wrapping the maven wrapper]]></title>
            <link>https://jangalinski.medium.com/wrapping-the-maven-wrapper-2df61e01639d?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/2df61e01639d</guid>
            <category><![CDATA[maven]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Fri, 21 May 2021 11:07:03 GMT</pubDate>
            <atom:updated>2021-05-25T14:16:25.358Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zC-hYQioXj1olOVj" /><figcaption>Photo by <a href="https://unsplash.com/@egnaro?utm_source=medium&amp;utm_medium=referral">Rick Mason</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Reproducible, cross platform builds are key in modern, distributed, CI based development. “It works on my machine” is just not got enough anymore.</p><p>This becomes a problem, when you locally build with your own central maven version (let’s say: latest, 3.8.1), while your colleagues still use 3.6.3 and your build server does something completely different. <br>When the build fails on any of those machines, could it be that the maven versions of the central installation, the configured Java version and the versions of used plugins are incompatible?</p><p>The <a href="https://github.com/takari/maven-wrapper">maven wrapper</a> (heavily inspired by the gradle pendant which is part of the gradle build system) addresses this issue. Instead of building with your central installation, each project can define its own specific maven version which is downloaded to your <em>~/.m2/wrapper/dists </em>directory and executed via the wrapper script <em>mvnw</em> (provided for *ix, mac and windows).</p><p>So far, so good. But now you have to remember to execute <em>./mvnw</em> instead of <em>mvn</em> everytime you build via CLI. This can a) become quite annoying and b) thus be forgotten.</p><h4>Shell function to the rescue!</h4><p>To overcome this situation, I have implemented a function (in my case initialized via my zsh setup via <em>$ZSH_HOME/custom/maven.zsh</em>, but you can choose any setup you like best) that when you type <em>mvn</em> in the console either takes the <em>mvnw</em> or the central installation as a fallback.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/198e6f5407695beec8995f757b858af1/href">https://medium.com/media/198e6f5407695beec8995f757b858af1/href</a></iframe><p>This script traverses up in the path hierarchy until it finds a <em>mvnw</em> script, if it reaches root (or your defined STOP), it falls back to the central installation.</p><h4>One note on gitignore</h4><p>Additional hint: with the latest version of the maven wrapper, you do no longer need to commit a binary jar to your repo, it automatically downloads the jar for you via a Java helper. So it is good practice to include this snippet in your <em>.gitignore</em> file when working with the wrapper:</p><p><em># </em><a href="https://github.com/takari/maven-wrapper#usage-without-binary-jar"><em>https://github.com/takari/maven-wrapper#usage-without-binary-jar</em></a><em><br>.mvn/wrapper/maven-wrapper.jar</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2df61e01639d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kotlin Data Class mapping]]></title>
            <link>https://medium.com/holisticon-consultants/kotlin-data-class-mapping-aa0f9f750ca1?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/aa0f9f750ca1</guid>
            <category><![CDATA[data-class]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[mapper]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Sun, 27 Oct 2019 08:31:01 GMT</pubDate>
            <atom:updated>2019-11-14T15:30:16.742Z</atom:updated>
            <content:encoded><![CDATA[<p>Working with <em>kotlin</em> and the <a href="https://axoniq.io/product-overview/axon-framework"><em>axonframework</em></a>, I found myself challenged with the task of mapping <em>kotlin</em> data classes with matching attributes but different types.</p><p>For example, consider I received a <em>command</em>:</p><pre><strong>data class </strong>WithdrawMoney(<br>  <strong>val accountId</strong>: String,<br>  <strong>val amount</strong>: Amount<br>) {<br>  <strong>data class </strong>Amount(<strong>val value</strong>:BigDecimal, <strong>val currency</strong>: Currency)<br>}</pre><p>and want to emit an <em>event</em>:</p><pre><strong>data class </strong>MoneyWithdrawn(<br>  <strong>val accountId</strong>: String,<br>  <strong>val amount</strong>: Amount<br>) {<br>  <strong>data class </strong>Amount(<strong>val value</strong>:BigDecimal, <strong>val currency</strong>: Currency)<br>}</pre><p>So I will have to implement a manual copy function that creates the event based on the command:</p><pre><strong>fun </strong>WithdrawMoney.createEvent() = MoneyWithdrawn(<br>  accountId = <strong>this</strong>.<strong>accountId</strong>,<br>  amount = <strong>Amount</strong>(<br>    value = <strong>this</strong>.<strong>amount</strong>.<strong>value</strong>,<br>    currency = <strong>this</strong>.<strong>amount</strong>.<strong>currency<br>  </strong>)<br>)</pre><p>which, as you can imagine will become a tedious, repetitive and thus error prone task if the number of fields and subtypes increases.</p><p>So I went looking for a mapping tool like <a href="https://mapstruct.org/"><em>mapstruct</em></a> or <a href="http://modelmapper.org/"><em>model-mapper</em></a> that I can use with data classes in <em>kotlin</em>. But currently, those do not support <em>kotlin</em> to an extend that would satisfy my needs. To get them working properly, one would either have to make fields in data classes <a href="https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin">mutable and nullable</a> (which contradicts the reasons that made me choose <em>kotlin</em> data classes for commands and events in the first place) or add a lot of manual code to the mapper tool, which questions the choice of a framework at all.</p><p>Luckily, <em>kotlin</em> provides <a href="https://kotlinlang.org/docs/reference/reflection.html"><em>kotlin-reflect</em></a> which allows us to call the primary data class constructor as a map of properties. So with the help of this very cool <a href="https://stackoverflow.com/questions/39199426/better-way-to-map-kotlin-data-objects-to-data-objects">discussion on stackoverflow</a> and another great <a href="https://www.baeldung.com/kotlin-data-objects">baeldung blog article</a>, I came up with this <em>DataClassMapper</em> class that works with reflection only and requires no additional frameworks:</p><pre><strong>// </strong>(1) import kotlin-reflect extension functions<strong><br>import </strong>kotlin.reflect.*<br><strong>import </strong>kotlin.reflect.full.<em>*</em></pre><pre><em>// (2) define alias for Mapper function<br></em><strong>typealias </strong>Mapper&lt;I, O&gt; = (I) -&gt; O</pre><pre><em>/**<br> * Mapper that can convert one data class into another data class.<br> *<br> * </em><strong><em>@param </em></strong><em>&lt;I&gt; inType (convert from)<br> * </em><strong><em>@param </em></strong><em>&lt;O&gt; outType (convert to)<br> */<br>// (3) class that gets input and output class and implements Mapper   (-&gt; 2)<br></em><strong>class </strong>DataClassMapper&lt;I : Any, O : Any&gt;(<br>    <strong>private val inType</strong>: KClass&lt;I&gt;, <br>    <strong>private val outType</strong>: KClass&lt;O&gt;<br>) : Mapper&lt;I, O&gt; {<br><br>  <strong>companion object </strong>{<br>    // (4) provide reified constructor that does not require passing of KClass attributes<br>    <strong>inline operator fun </strong>&lt;<strong>reified </strong>I : Any, <strong>reified </strong>O : Any&gt; invoke() = DataClassMapper(I::<strong>class</strong>, O::<strong>class</strong>)<br><br>    <strong>fun </strong>&lt;I : Any, O : Any&gt; setMapper(mapper: Mapper&lt;I, O&gt;) = <strong>object </strong>: Mapper&lt;Set&lt;I&gt;, Set&lt;O&gt;&gt; {<br>      <strong>override fun </strong>invoke(data: Set&lt;I&gt;): Set&lt;O&gt; = <br>            data.<em>map</em>(mapper).<em>toSet</em>()<br>}<br>  }<br></pre><pre>  // (5) get constructor of target data class<br>  <strong>private val outConstructor </strong>= <strong>outType</strong>.<em>primaryConstructor</em>!!<br>  // (6) get properties from input type (lazily)<br>  <strong>private val inPropertiesByName by </strong><em>lazy </em><strong>{ inType</strong>.<em>memberProperties</em>.<em>associateBy </em><strong>{ it</strong>.<strong>name } }<br><br>  // </strong>(8) get value of source data via reflection or call registered converter<strong><br>  private fun </strong>argFor(parameter: KParameter, data: I): Any? {<br>    <em>// get value from input data ...<br>    </em><strong>val </strong>value = <strong>inPropertiesByName</strong>[parameter.<strong>name</strong>]?.get(data) ?: <strong>return null<br><br>    </strong><em>// if a special mapper is registered, use it, otherwise keep value<br>    </em><strong>return fieldMappers</strong>[parameter.<strong>name</strong>]?.invoke(value) ?: value<br>  }</pre><pre><strong>  // </strong>(7) call the target constructor with a map of source attributes and values, where argFor defines the value to use (see (8)<strong><br>  override fun </strong>invoke(data: I): O = <em>with</em>(<strong>outConstructor</strong>) <strong>{<br>    </strong>callBy(<strong>parameters</strong>.<em>associateWith </em><strong>{ </strong>argFor(<strong>it</strong>, data) <strong>}</strong>)<br>  <strong>}</strong></pre><pre><strong>  // </strong>(9) register converter classes on attribute level to support transitive conversions or collection types<strong><br>  val fieldMappers </strong>= <em>mutableMapOf</em>&lt;String, Mapper&lt;Any, Any&gt;&gt;()<br>  <strong>inline fun </strong>&lt;<strong>reified </strong>S : Any, <strong>reified </strong>T : Any&gt; register(parameterName: String, <strong>crossinline </strong>mapper: Mapper&lt;S, T&gt;): DataClassMapper&lt;I, O&gt; = <em>apply </em><strong>{<br>    // </strong>to be able to store the sub-mappers, we have to wrap them as (Any) -&gt; Any<strong><br>    this</strong>.<strong>fieldMappers</strong>[parameterName] = <strong>object </strong>: Mapper&lt;Any, Any&gt; {<br>      <strong>override fun </strong>invoke(data: Any): Any = mapper.invoke(data <strong>as </strong>S)<br>    }<br>  <strong>}<br><br></strong>}</pre><p>Using this class, you can now define mappers for each (sub-)type and provide composition of mappers for complex data structures like this:</p><pre>// sub mapper for Amount (using the reified constructor (4))<strong><br>val </strong>amountMapper = DataClassMapper&lt;WithdrawMoney.Amount, MoneyWithdrawn.Amount&gt;()</pre><pre>// mapper from command to event using the amountMapper<br><strong>val </strong>commandMapper = DataClassMapper&lt;WithdrawMoney, MoneyWithdrawn&gt;()<br>  .register(<strong>&quot;amount&quot;</strong>, amountMapper)<br><br>// actual creation of event based on command types <strong><br>val </strong>event = commandMapper(command)</pre><p>The <a href="https://github.com/jangalinski/kotlin-dataclass-mapper/blob/master/src/main/kotlin/DataClassMapper.kt"><em>DataClassMapper</em></a> is available on my <a href="https://github.com/jangalinski">github</a> repository. If this proves to be useful, I will publish a mapper-lib on jcenter, but so far, just copy it to your project and start exploring and giving feedback.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aa0f9f750ca1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/holisticon-consultants/kotlin-data-class-mapping-aa0f9f750ca1">Kotlin Data Class mapping</a> was originally published in <a href="https://medium.com/holisticon-consultants">Holisticon Consultants</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kotlin property delegation lazy/eager]]></title>
            <link>https://jangalinski.medium.com/kotlin-property-delegation-lazy-eager-469eddcf54a7?source=rss-297171ba3cfd------2</link>
            <guid isPermaLink="false">https://medium.com/p/469eddcf54a7</guid>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Jan Galinski]]></dc:creator>
            <pubDate>Mon, 11 Feb 2019 21:05:46 GMT</pubDate>
            <atom:updated>2019-02-11T21:05:46.379Z</atom:updated>
            <content:encoded><![CDATA[<p>The following class uses two ways of property delegation with Kotlin.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a7eefff8912e3c11829b8704e23286a5/href">https://medium.com/media/a7eefff8912e3c11829b8704e23286a5/href</a></iframe><p>Foo exposes two read only properties ‘eager’ and ‘lazy’ which both are backed by the private ‘count()’ function.</p><p>If you access ‘eager’, you always get a new increased value of count, if you access ‘lazy’, it will increase count once and afterwards forever keep that value:</p><blockquote>lazy: 1<br>lazy: 1<br>eager: 2<br>eager: 3<br>lazy: 1</blockquote><p>You can also use these as extension vals on existing classes …</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=469eddcf54a7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>