<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bruno Pacheco</title>
    <description>The latest articles on DEV Community by Bruno Pacheco (@brunopacheco1).</description>
    <link>https://dev.to/brunopacheco1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F277717%2F5ab7bcdd-3010-492b-9d06-8adf1478fc2c.jpeg</url>
      <title>DEV Community: Bruno Pacheco</title>
      <link>https://dev.to/brunopacheco1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brunopacheco1"/>
    <language>en</language>
    <item>
      <title>Rebuilding Praecepta: From a Trie to Bitsets, and a Fair Benchmark Against Camunda and Drools</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Sat, 07 Nov 2026 21:38:35 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/rebuilding-praecepta-from-a-trie-to-bitsets-and-a-fair-benchmark-against-camunda-and-drools-3f4p</link>
      <guid>https://dev.to/brunopacheco1/rebuilding-praecepta-from-a-trie-to-bitsets-and-a-fair-benchmark-against-camunda-and-drools-3f4p</guid>
      <description>&lt;p&gt;In January 2023 I wrote about &lt;a href="https://bruno.pacheco.lu/posts/optimizing-decision-making-with-trie-tree/" rel="noopener noreferrer"&gt;optimizing decision-making with a trie&lt;/a&gt;. The idea worked in principle: part of it was implemented, and is still maintained, at &lt;a href="https://www.pictet.com/mc/en/about/pictet-tech" rel="noopener noreferrer"&gt;Pictet Tech&lt;/a&gt;. The numbers looked great, as you can see in my previous post.&lt;/p&gt;

&lt;p&gt;Going through my old repositories for opportunities to test &lt;a href="https://claude.ai/new" rel="noopener noreferrer"&gt;Claude&lt;/a&gt;, I decided to explore the major problem of that solution: the trie's memory exploded as tables grew; a generated table of 1,000 rules took &lt;strong&gt;over 1 GB&lt;/strong&gt;. And during the development process, Claude told me that the first benchmark was not entirely fair, as Praecepta ran a different hit policy from Camunda and Kogito, and two of the services read one input from the wrong field.&lt;/p&gt;

&lt;p&gt;So I refactored the engine and the benchmark, and completed the Maven plugin that compiles tables at build time. The result of the refactoring can be found in &lt;a href="https://github.com/brunopacheco1/praecepta" rel="noopener noreferrer"&gt;Praecepta 0.1.0&lt;/a&gt; on Maven Central. The principles are still the same: a small, dependency-free Java engine for DMN decision tables. It now also has a &lt;a href="https://bruno.pacheco.lu/praecepta/" rel="noopener noreferrer"&gt;website&lt;/a&gt; for the guide and the API documentation.&lt;/p&gt;

&lt;p&gt;This post covers what changed, a benchmark against Camunda and Drools that I tried hard to make fair (including where it flatters Praecepta, and where Praecepta is slower), and what those engines do that Praecepta does not. This post also covers how open Praecepta is, and what could come next. That last part is where I would love your opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a trie to bitsets
&lt;/h2&gt;

&lt;p&gt;A decision table is a simple list of rules. Each rule tests some input columns (&lt;code&gt;country is "PT" or "ES"&lt;/code&gt;, &lt;code&gt;age in [18..65)&lt;/code&gt;, or "anything") and produces outputs. The previous solution, a custom implementation of a &lt;a href="https://www.geeksforgeeks.org/dsa/trie-insert-and-search/" rel="noopener noreferrer"&gt;trie&lt;/a&gt;, stored every possible path through the columns, and every "anything" cell multiplied the paths. That multiplication was the memory problem!&lt;/p&gt;

&lt;p&gt;I tried a couple of things back then, including quantum computing via &lt;a href="https://github.com/brunopacheco1/qengine/tree/main/src/main/java/com/github/brunopacheco1/qengine" rel="noopener noreferrer"&gt;qengine&lt;/a&gt;: encoding the input as bits and each rule as quantum logic gates, to see whether a quantum circuit could beat the trie. It ran only on a simulator, but the thought stayed with me: rule matching is boolean logic over bits. The bitset index is the classical, practical version of that thought.&lt;/p&gt;

&lt;p&gt;In the end, I stopped thinking about it. But now, in 2026, and with Claude's support, the new core uses &lt;strong&gt;bitsets&lt;/strong&gt;. Every rule gets one bit. For every column, each distinct value the rules can tell apart gets a mask: the set of rules that accept it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column &lt;code&gt;country&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;rule 1&lt;/th&gt;
&lt;th&gt;rule 2&lt;/th&gt;
&lt;th&gt;rule 3&lt;/th&gt;
&lt;th&gt;rule 4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"PT"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"FR"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;any other value&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rule 1 tests &lt;code&gt;"PT"&lt;/code&gt;, rule 2 tests &lt;code&gt;"FR"&lt;/code&gt;, rule 3 is "anything" (so it is in every mask), and rule 4 is &lt;code&gt;not("FR")&lt;/code&gt;. Evaluating an input looks up one mask per column and ANDs them together. A 64-bit AND checks 64 rules at once, and the bundled 119-rule table fits in two words. Memory grows with columns × values × rules, never with the product of values, and the 1,000-rule table that needed 1 GB now takes &lt;strong&gt;0.6 MB&lt;/strong&gt;, and reading it from DMN and building it takes about a quarter of a second in a fresh JVM.&lt;/p&gt;

&lt;p&gt;And the best part: the hit policy falls out of the bits too. Bits are numbered in rule order (or priority order), so FIRST is "lowest set bit", which is a single CPU instruction.&lt;/p&gt;

&lt;p&gt;Numbers and dates work the same way: the boundaries the rules mention (&lt;code&gt;18&lt;/code&gt;, &lt;code&gt;65&lt;/code&gt;, &lt;code&gt;date("2025-01-01")&lt;/code&gt;) split each column into intervals, a binary search finds the interval, and each interval has its mask.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else changed
&lt;/h2&gt;

&lt;p&gt;The core was not the only change. Praecepta now also has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict DMN 1.3 hit policies:&lt;/strong&gt; UNIQUE, ANY, PRIORITY, FIRST, OUTPUT ORDER, RULE ORDER, and COLLECT with SUM, MIN and MAX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FEEL cells&lt;/strong&gt; on string, number and date columns: lists, &lt;code&gt;not(...)&lt;/code&gt;, comparisons, intervals and &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation when the table is built:&lt;/strong&gt; overlapping rules, conflicting outputs, rules that can never fire, and &lt;em&gt;gaps&lt;/em&gt; (inputs no rule covers, with an example).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DMN import and export:&lt;/strong&gt; Praecepta reads DMN 1.1–1.5 files, including those from Camunda Modeler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Maven plugin&lt;/strong&gt; that validates tables at build time and generates typed input and output records, so your application ships only the engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java 21+&lt;/strong&gt;, with the engine as a JPMS module. Everything is tested on Java 21, 25 and 27.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;Since the first benchmark was not fair, I rebuilt it from scratch. This time Praecepta runs against Camunda 8 and Drools (the DMN engine inside Kogito), and every engine gets exactly the same work.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I measured it
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same rules, same file, for every engine.&lt;/strong&gt; Each table is written once as a DMN document, and every engine, Praecepta included, reads that document, as an application loading a &lt;code&gt;.dmn&lt;/code&gt; file would. Before any timing, every engine evaluates the whole workload and its answers are compared with Praecepta built straight from the rules. The run aborts if any engine disagrees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supported, default setups:&lt;/strong&gt; Camunda 8's DMN engine (dmn-scala 1.12.6, the one Zeebe embeds) and Drools 10.2.0's DMN runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same input for all:&lt;/strong&gt; a &lt;code&gt;Map&lt;/code&gt; of values. Praecepta's fastest path (generated classes with enums) is shown separately and labelled as its best case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many tables:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real:&lt;/strong&gt; the 119-rule table from 2023 (values blurred, FIRST hit policy).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated, &lt;code&gt;synthetic&lt;/code&gt;:&lt;/strong&gt; string columns only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated, &lt;code&gt;mixed&lt;/code&gt;:&lt;/strong&gt; string, number and date columns, with comparisons, intervals and &lt;code&gt;not(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sizes:&lt;/strong&gt; the generated tables run from 10 to 10,000 rules and from 5 to 20 columns. The same 1,000 rules are also run under each hit policy, and with inputs that match no rule.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More than an average:&lt;/strong&gt; latency percentiles, memory allocated per decision, throughput with 1, 4 and 8 threads sharing one engine, the time to build an engine, and the time to the first decision in a fresh JVM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The released engine:&lt;/strong&gt; I measured the same engine code that is in the 0.1.0 release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine:&lt;/strong&gt; Apple M2 Pro (8 performance cores), 16 GB, macOS 26.7.1, OpenJDK 27.0.1. JMH: 5 × 2 s warm-up and 5 × 3 s measurement iterations, 3 forks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is public:&lt;/strong&gt; &lt;a href="https://github.com/brunopacheco1/praecepta/tree/main/praecepta-benchmarks/results/2026-11-07-apple-m2-pro-full" rel="noopener noreferrer"&gt;the results&lt;/a&gt; keep every log, the raw JMH output and the environment, and one script reruns the whole suite in about 2.5 hours. To check that the picture is not an artefact of my machine, I also ran the short profile on a &lt;a href="https://github.com/brunopacheco1/praecepta/actions/runs/42391587868" rel="noopener noreferrer"&gt;GitHub Actions runner&lt;/a&gt; (AMD EPYC, 4 vCPUs, Linux, Java 25). It used one fork, so its numbers are noisy, and its absolute times are 1.5–3× higher, but the ranking holds on every table. On mixed-1000, for example, Praecepta took 1.0 µs, Camunda 519 µs and Drools 421 µs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Latency as tables grow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0vecqmib3zh1xo7tvfwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0vecqmib3zh1xo7tvfwi.png" alt="Time per evaluation on mixed tables, log scale: Praecepta stays between 0.3 and 1.5 µs from 10 to 10,000 rules, while Camunda and Drools grow from about 7 µs to about 3,100 µs" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;µs per evaluation&lt;/th&gt;
&lt;th&gt;Praecepta 0.1.0&lt;/th&gt;
&lt;th&gt;Camunda 8&lt;/th&gt;
&lt;th&gt;Drools 10.2.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;real (119 rules)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.19&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.3&lt;/td&gt;
&lt;td&gt;43.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic-10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.20&lt;/td&gt;
&lt;td&gt;5.89&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic-100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.1&lt;/td&gt;
&lt;td&gt;14.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic-1000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic-10000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.67&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,333&lt;/td&gt;
&lt;td&gt;1,419&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixed-10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.31&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.05&lt;/td&gt;
&lt;td&gt;7.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixed-100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.44&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;33.2&lt;/td&gt;
&lt;td&gt;31.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixed-1000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.64&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;263&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixed-10000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.51&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,118&lt;/td&gt;
&lt;td&gt;3,122&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bigger the table, the bigger the gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Praecepta:&lt;/strong&gt; from 10 to 10,000 mixed rules, its time goes from 0.31 to 1.51 µs, 5× slower for 1,000× more rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camunda and Drools:&lt;/strong&gt; their time grows roughly with the number of rules, from about 7 µs to about 3,100 µs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ratio:&lt;/strong&gt; Praecepta is 25–40× faster on 10 rules, 70–120× on 100, 400–600× on 1,000 and about 2,000× on 10,000. On the real table it is 104× faster than Camunda and 233× faster than Drools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More columns cost Praecepta little: with 1,000 rules, going from 6 to 18 mixed columns takes it from 0.62 to 1.12 µs. Number and date columns cost it 2–3× string columns, because a binary search over decimals is slower than a hash lookup; that one has &lt;a href="https://github.com/brunopacheco1/praecepta/issues/16" rel="noopener noreferrer"&gt;an issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not every number is equally precise: 51 of the 63 latency measurements have a 99.9% confidence interval within ±10%. The widest is Drools on synthetic-10000, at ±33%. All of them are in the results, if you want to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hit policies, and inputs that match no rule
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;µs per evaluation, 1,000 mixed rules&lt;/th&gt;
&lt;th&gt;Praecepta 0.1.0&lt;/th&gt;
&lt;th&gt;Camunda 8&lt;/th&gt;
&lt;th&gt;Drools 10.2.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;COLLECT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.64&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;263&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIRST&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.53&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;49.6&lt;/td&gt;
&lt;td&gt;248&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRIORITY&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;287&lt;/td&gt;
&lt;td&gt;252&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RULE ORDER&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.64&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;285&lt;/td&gt;
&lt;td&gt;257&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UNIQUE (1,000 rules that never overlap)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.23&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;161&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No rule matches (COLLECT)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.32&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;261&lt;/td&gt;
&lt;td&gt;197&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Praecepta:&lt;/strong&gt; the hit policy barely changes its time, because it is one operation on the bits that matched. An input that matches nothing costs no more than one that matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camunda:&lt;/strong&gt; it is 5× faster under FIRST, which suggests it stops at the first match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drools:&lt;/strong&gt; it takes about the same time under every policy. When nothing matches, it still pays about three quarters of the time of a match, and Camunda nearly all of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The real table&lt;/strong&gt; has no such inputs: its last rule matches anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tail latency and memory per decision
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;µs per evaluation&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;th&gt;p99.9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Praecepta&lt;/strong&gt;, real&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.17&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.54&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camunda 8, real&lt;/td&gt;
&lt;td&gt;17.7&lt;/td&gt;
&lt;td&gt;32.4&lt;/td&gt;
&lt;td&gt;41.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drools 10.2.0, real&lt;/td&gt;
&lt;td&gt;51.8&lt;/td&gt;
&lt;td&gt;69.8&lt;/td&gt;
&lt;td&gt;85.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Praecepta&lt;/strong&gt;, mixed-1000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.67&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.57&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camunda 8, mixed-1000&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;323&lt;/td&gt;
&lt;td&gt;444&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drools 10.2.0, mixed-1000&lt;/td&gt;
&lt;td&gt;260&lt;/td&gt;
&lt;td&gt;309&lt;/td&gt;
&lt;td&gt;386&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tails:&lt;/strong&gt; Praecepta's 99.9th percentile on mixed-1000 (2.6 µs) is still about 100× below the others' median.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The slowest decisions:&lt;/strong&gt; each engine's single slowest decision took up to a few milliseconds, most likely from pauses in the JVM or the operating system, which every engine sees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocation:&lt;/strong&gt; on tables of up to 1,000 rules, Praecepta allocates 32 to 466 bytes per decision. Camunda allocates 22 KB to 1.5 MB, and Drools 16 KB to 375 KB. Less garbage means less garbage collection under load.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Throughput with threads
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8dmvouof5g6qse1rb4gm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8dmvouof5g6qse1rb4gm.png" alt="Decisions per millisecond on the real table with 1, 4 and 8 threads, log scale: Praecepta 5,374 to 33,796, Camunda 61 to 451, Drools 21 to 146" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, all three engines do well: they can be shared between threads, and all of them scale.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;From 1 to 8 threads:&lt;/strong&gt; Praecepta goes from 5,374 to 33,796 decisions per millisecond (6.3×), Camunda from 61 to 451 (7.4×) and Drools from 21 to 146 (7.0×).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locks:&lt;/strong&gt; Praecepta's engine is immutable, so threads share it without any.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mixed-1000, 8 threads:&lt;/strong&gt; 11,721 decisions per millisecond for Praecepta, against 29 for Camunda and 23 for Drools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Start-up and building
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ms&lt;/th&gt;
&lt;th&gt;Praecepta 0.1.0&lt;/th&gt;
&lt;th&gt;Camunda 8&lt;/th&gt;
&lt;th&gt;Drools 10.2.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read the DMN and build the engine, warm JVM: real&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22.6&lt;/td&gt;
&lt;td&gt;36.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read the DMN and build the engine, warm JVM: mixed-1000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;437&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First decision in a fresh JVM: real&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;76&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;469&lt;/td&gt;
&lt;td&gt;379&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First decision in a fresh JVM: mixed-1000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;240&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;774&lt;/td&gt;
&lt;td&gt;1,005&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"First decision" starts a new JVM, reads the DMN file, builds the engine and evaluates one input. On the real table, Praecepta answers in 76 ms, 5–6× sooner than the others. That matters for command-line tools, serverless functions and test suites, and much less for long-running services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ocu7grjo9ibegl0hkws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ocu7grjo9ibegl0hkws.png" alt="Retained heap after building mixed tables, log scale: at 10,000 rules Praecepta holds 5 MB, Camunda 133 MB and Drools 108 MB" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Retained heap" is the heap still in use after building the engine in a fresh JVM and running a garbage collection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At 10,000 rules:&lt;/strong&gt; Praecepta holds 5 MB, against 133 MB for Camunda and 108 MB for Drools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Across all the generated tables:&lt;/strong&gt; it uses 6 to 88 times less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The real table:&lt;/strong&gt; Praecepta's footprint there was too small for the measurement to resolve (under 0.5 MB).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Praecepta is slower: building FIRST and PRIORITY tables
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build time in a fresh JVM, 1,000 mixed rules&lt;/th&gt;
&lt;th&gt;Praecepta 0.1.0&lt;/th&gt;
&lt;th&gt;Camunda 8&lt;/th&gt;
&lt;th&gt;Drools 10.2.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;COLLECT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.30 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.87 s&lt;/td&gt;
&lt;td&gt;1.17 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RULE ORDER&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.27 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.83 s&lt;/td&gt;
&lt;td&gt;1.07 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIRST&lt;/td&gt;
&lt;td&gt;3.6 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.85 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRIORITY&lt;/td&gt;
&lt;td&gt;68.7 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.82 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.12 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When it builds a FIRST or PRIORITY table, Praecepta looks for rules that can never fire: the validation described above. The check is exact, and on 1,000 random, overlapping rules it is expensive: 3.6 s under FIRST, and over a minute under PRIORITY. Evaluation is not affected, and the real 119-rule FIRST table builds in 74 ms. Still, a minute is not acceptable, and fixing it is on my list: &lt;a href="https://github.com/brunopacheco1/praecepta/issues/29" rel="noopener noreferrer"&gt;issue #29&lt;/a&gt; tracks making the check optional, bounded or cheaper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Praecepta's best case
&lt;/h3&gt;

&lt;p&gt;Feeding a &lt;code&gt;Map&lt;/code&gt; costs two hash lookups per column: one to read the value, one to find its mask. With the Maven plugin, inputs are generated records, and string columns with a value list become enums, whose masks are found by position instead of by hashing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frlr6kpw00jt22xxpux53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frlr6kpw00jt22xxpux53.png" alt="Praecepta input paths on the real table: Map of strings 231 ns, generated record with strings 142 ns, generated record with enums 101 ns" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is about &lt;strong&gt;100 ns&lt;/strong&gt; per decision on the real table, from an earlier run of the same code. But to keep the comparison fair, everything above uses &lt;code&gt;Map&lt;/code&gt; inputs for every engine, Praecepta included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being fair: what the numbers do not say
&lt;/h2&gt;

&lt;p&gt;Numbers like these are easy to oversell, so here is what they do not say.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the comparison flatters Praecepta, or is limited:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Praecepta does less work.&lt;/strong&gt; Camunda and Drools are general FEEL interpreters. Even when a cell is a simple test, they evaluate it through machinery that can handle arbitrary expressions, and they return richer results. Praecepta only supports what it can index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is still a microbenchmark.&lt;/strong&gt; It runs in-process, on one machine. A real service adds HTTP and serialisation, which usually cost far more than 20 µs, so in many systems the engine is not the bottleneck at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated tables are not real ones.&lt;/strong&gt; Their rules are random, so they overlap in ways real tables may not. Only one real table was measured: the DMN conformance kit's tables all use boolean columns, which Praecepta does not support yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every option was measured.&lt;/strong&gt; Drools' compiled (executable) model, generated at build time by Kogito, was not measured; it may be faster than the runtime DMN path I used. Drools' experimental "alpha network" option gave wrong answers on these tables in 10.2.0, so the harness refused to time it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What Camunda and Drools do that Praecepta does not:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full FEEL:&lt;/strong&gt; functions, arithmetic, contexts, lists, and tests across several columns (&lt;code&gt;income &amp;gt; 2 * rent&lt;/code&gt;). Praecepta cells are FEEL &lt;em&gt;simple unary tests&lt;/em&gt; on one column.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision requirement graphs:&lt;/strong&gt; decisions feeding other decisions, business knowledge models and boxed expressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More types:&lt;/strong&gt; boolean, time and date-time columns (planned).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling and services:&lt;/strong&gt; visual modellers, decision services, process engines, audit and explanation of decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proven conformance:&lt;/strong&gt; Drools runs the official DMN conformance kit; Praecepta has not been run against it yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maturity:&lt;/strong&gt; large communities, years of production use, and commercial support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if your decisions are mostly decision tables with simple cell tests, and latency, memory or start-up matter to you, give Praecepta a try. If you need full DMN, Camunda and Drools are the better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;Using it is simple. First, add the engine (it has no dependencies):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.brunopacheco1&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;praecepta-engine&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then load a DMN file, bind the inputs, and evaluate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="nc"&gt;DecisionTable&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dmn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"discount.dmn"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DmnReader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dmn&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PraeceptaEngine&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                              &lt;span class="c1"&gt;// hit policy, column types, value lists, rules&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Customer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;numberInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Customer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tier"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evaluate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;          &lt;span class="c1"&gt;// e.g. [gold]&lt;/span&gt;
&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validationReport&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                         &lt;span class="c1"&gt;// overlaps, unreachable rules, gaps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or let the Maven plugin compile your tables (Excel, CSV or DMN) at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.brunopacheco1&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;praecepta-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&amp;lt;execution&amp;gt;&amp;lt;goals&amp;gt;&amp;lt;goal&amp;gt;&lt;/span&gt;generate&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&amp;lt;/goals&amp;gt;&amp;lt;/execution&amp;gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;packageName&amp;gt;&lt;/span&gt;com.acme.pricing&lt;span class="nt"&gt;&amp;lt;/packageName&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tables&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&amp;lt;source&amp;gt;&lt;/span&gt;src/main/rules/discount.dmn&lt;span class="nt"&gt;&amp;lt;/source&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tables&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DiscountOutput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Discount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evaluate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DiscountInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DiscountInput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build fails if the table has errors (for example, two UNIQUE rules that overlap), and only the engine is needed at runtime. The &lt;a href="https://github.com/brunopacheco1/praecepta#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt; has the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How open is it?
&lt;/h2&gt;

&lt;p&gt;I wanted a small project that is easy to trust. Here is what that means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Licence:&lt;/strong&gt; Apache-2.0. The repository is &lt;a href="https://reuse.software" rel="noopener noreferrer"&gt;REUSE&lt;/a&gt;-compliant: every file says who wrote it and under which licence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything in public:&lt;/strong&gt; the code, the CI, the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/docs/MODERNISATION.md" rel="noopener noreferrer"&gt;modernisation plan&lt;/a&gt; phase by phase, the benchmark harness and every result, and the issues. So are the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/docs/ROADMAP.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;, the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/docs/ARCHITECTURE.md" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;, the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/docs/SECURITY-ASSURANCE.md" rel="noopener noreferrer"&gt;security assurance case&lt;/a&gt; and the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;governance&lt;/a&gt;, which also says who takes over if I stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release integrity:&lt;/strong&gt; every artifact is GPG-signed and ships with an SBOM (CycloneDX), build provenance (SLSA, verifiable with &lt;code&gt;gh attestation verify&lt;/code&gt;) and third-party notices. The builds are reproducible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checks on every change:&lt;/strong&gt; tests on three Java versions, plugin tests on three Maven versions, an enforced code style with compiler warnings as errors, a SonarCloud quality gate, CodeQL, dependency licence and vulnerability checks, and fuzzing of the two parsers that read untrusted input, on every pull request that touches the engine and for longer every week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A protected &lt;code&gt;main&lt;/code&gt;:&lt;/strong&gt; signed commits only, and every change arrives through a pull request that passes the checks and is approved by a code owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Badges from independent issuers:&lt;/strong&gt; the &lt;a href="https://www.bestpractices.dev/projects/16426" rel="noopener noreferrer"&gt;OpenSSF Best Practices&lt;/a&gt; &lt;strong&gt;silver&lt;/strong&gt; badge and OpenSSF Baseline level 1 (every answer links to its evidence), &lt;a href="https://api.reuse.software/info/github.com/brunopacheco1/praecepta" rel="noopener noreferrer"&gt;REUSE compliance&lt;/a&gt; checked by the FSFE, and the &lt;a href="https://scorecard.dev/viewer/?uri=github.com/brunopacheco1/praecepta" rel="noopener noreferrer"&gt;OpenSSF Scorecard&lt;/a&gt;, which rechecks the repository every week: &lt;strong&gt;8.8 / 10&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapi.scorecard.dev%2Fprojects%2Fgithub.com%2Fbrunopacheco1%2Fpraecepta%2Fbadge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapi.scorecard.dev%2Fprojects%2Fgithub.com%2Fbrunopacheco1%2Fpraecepta%2Fbadge" alt="OpenSSF Scorecard" width="136" height="20"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lowest Scorecard item is &lt;strong&gt;code review&lt;/strong&gt;, and it is worth explaining. Anyone can fork the project and open a pull request. The check measures something else: whether each change that reached &lt;code&gt;main&lt;/code&gt; was approved by someone other than its author. Today that is zero, because I am the only maintainer, and nobody can approve their own changes. The same reason keeps branch protection from a full score: I merge my own pull requests with an administrator's bypass. Every contribution I review raises the first, and a second maintainer would close both. The &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;governance&lt;/a&gt; explains how to become one. Which brings me to the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to help?
&lt;/h2&gt;

&lt;p&gt;If you would like to contribute, the &lt;a href="https://bruno.pacheco.lu/praecepta/" rel="noopener noreferrer"&gt;website&lt;/a&gt; gathers the guide, the API documentation and how the engine works, and the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributing guide&lt;/a&gt; explains how to build and test it. Here are some good first issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/brunopacheco1/praecepta/issues/13" rel="noopener noreferrer"&gt;string ranges in cells&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/brunopacheco1/praecepta/issues/15" rel="noopener noreferrer"&gt;a COUNT aggregation&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/brunopacheco1/praecepta/issues/22" rel="noopener noreferrer"&gt;running the DMN conformance kit&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would also welcome reviewers and a co-maintainer: the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;governance&lt;/a&gt; describes the roles and how decisions are made.&lt;/p&gt;

&lt;h2&gt;
  
  
  What could come next?
&lt;/h2&gt;

&lt;p&gt;These are the features I think would be useful; the &lt;a href="https://github.com/brunopacheco1/praecepta/blob/main/docs/ROADMAP.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; puts them in order for the next year. &lt;strong&gt;Tell me which ones you would use&lt;/strong&gt;: a 👍 on the issue is enough, and a comment if you would like to build it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/12" rel="noopener noreferrer"&gt;Boolean, time and date-time columns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/13" rel="noopener noreferrer"&gt;String ranges in cells&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/25" rel="noopener noreferrer"&gt;Inputs with several values, e.g. more than one nationality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/14" rel="noopener noreferrer"&gt;Decision requirement graphs (chained decisions)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/15" rel="noopener noreferrer"&gt;A COUNT aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/16" rel="noopener noreferrer"&gt;Faster number and date columns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/17" rel="noopener noreferrer"&gt;Compressed bitmaps for very large tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/18" rel="noopener noreferrer"&gt;A Gradle plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/19" rel="noopener noreferrer"&gt;GraalVM native-image support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/20" rel="noopener noreferrer"&gt;A Spring Boot starter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/21" rel="noopener noreferrer"&gt;An online playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/brunopacheco1/praecepta/issues/22" rel="noopener noreferrer"&gt;Running the DMN conformance kit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The 2023 trie proved the idea and hit a wall. Bitsets took it further: on these tables, 20 to 2,000 times faster than general-purpose DMN engines, with 6 to 88 times less memory, and honest about what it does not do, including a build that is still slow for large FIRST and PRIORITY tables. Give it a try, tell me where it breaks, and tell me what you would like it to do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namárië | See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
      <category>performance</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Teaching a Machine to Say Namárië</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Thu, 05 Nov 2026 20:27:04 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/teaching-a-machine-to-say-namarie-4bi3</link>
      <guid>https://dev.to/brunopacheco1/teaching-a-machine-to-say-namarie-4bi3</guid>
      <description>&lt;p&gt;In my very &lt;a href="https://dev.to/posts/paran-quenya-part-1/"&gt;first Quenya post&lt;/a&gt; (so proud of myself!), I listed &lt;a href="https://annie-zhou1997.github.io/QuenyaTTS.github.io/" rel="noopener noreferrer"&gt;QuenyaTTS&lt;/a&gt; among the tools worth knowing: a text-to-speech system for Quenya built by &lt;a href="https://github.com/Annie-Zhou1997/" rel="noopener noreferrer"&gt;Annie Zhou&lt;/a&gt; for her 2024 master's thesis at the University of Groningen.&lt;/p&gt;

&lt;p&gt;I found this project quite interesting and thought that perhaps I could improve it by trying a few different techniques. First, I mined some issues of Parma Eldalamberon (published by the &lt;a href="https://www.elvish.org/" rel="noopener noreferrer"&gt;Elvish Linguistic Fellowship&lt;/a&gt;) for pronunciation rules that Annie's code might be missing. Second, I tried to extract the phonetics from a recording of Tolkien himself reciting Namárië, &lt;a href="https://www.youtube.com/watch?v=mVACqgDqLno" rel="noopener noreferrer"&gt;published by the Romanian Tolkien Society&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is about what changed, and about what happened when I put the synthetic voice next to Tolkien's.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Annie's system works
&lt;/h2&gt;

&lt;p&gt;A text-to-speech system like this one works in two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;From text to sounds.&lt;/strong&gt; A set of rules turns written Quenya into phonetic symbols (IPA). &lt;em&gt;Elen síla lúmenn' omentielvo&lt;/em&gt; becomes &lt;code&gt;ˈɛlɛn sˈiːla lˈuːmɛnn ɔmɛntiˈɛlvɔ&lt;/code&gt;: which sounds to make and where the stress falls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From sounds to audio.&lt;/strong&gt; A neural model, &lt;a href="https://github.com/DigitalPhonetics/IMS-Toucan" rel="noopener noreferrer"&gt;IMS-Toucan&lt;/a&gt; from the University of Stuttgart, turns those symbols into a voice.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What changed in Annie's code
&lt;/h2&gt;

&lt;p&gt;I used Claude to find gaps in how Annie's code converts Quenya into the International Phonetic Alphabet. The rules were rewritten and checked against Appendix E of The Lord of the Rings, &lt;a href="https://eldamo.org/" rel="noopener noreferrer"&gt;Eldamo&lt;/a&gt;, several published pronunciation guides, and Tolkien's own notes in Parma Eldalamberon. Some examples: &lt;em&gt;hr&lt;/em&gt; and &lt;em&gt;hl&lt;/em&gt; are read as plain &lt;em&gt;r&lt;/em&gt; and &lt;em&gt;l&lt;/em&gt;, &lt;em&gt;w&lt;/em&gt; becomes &lt;em&gt;v&lt;/em&gt; only at the start of a word, archaic spellings such as &lt;em&gt;þ&lt;/em&gt; are read with their Third Age sound, and stress follows the Appendix E rule (never the last syllable; the second-to-last if it is heavy, otherwise the third-to-last). Every rule now has a test.&lt;/p&gt;

&lt;p&gt;Annie's original project contained a modified copy of an older Toucan. Now the &lt;a href="https://github.com/brunopacheco1/Quenya-TTS" rel="noopener noreferrer"&gt;Quenya-TTS repository&lt;/a&gt; holds only the Quenya parts, and Toucan comes in unchanged, at a fixed version. Quenya is given to Toucan as phonetic symbols, and since Toucan has never heard Quenya, it borrows the settings of Finnish, Tolkien's own inspiration for the language.&lt;/p&gt;

&lt;p&gt;Toucan starts speaking at the very first moment of the file, so the first syllable came out clipped. The tool now adds a short silent pause before the first sound. A side effect was that the model filled that pause with a little noise, heard as an extra &lt;em&gt;k&lt;/em&gt; or &lt;em&gt;p&lt;/em&gt; before the first word, so that pause is now muted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing with Tolkien
&lt;/h2&gt;

&lt;p&gt;Tolkien recorded himself reciting Namárië. I wrote a small script that compares any recording with our output, line by line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A speech recogniser for many languages (&lt;a href="https://huggingface.co/facebook/wav2vec2-xlsr-53-espeak-cv-ft" rel="noopener noreferrer"&gt;wav2vec2 XLSR&lt;/a&gt;) writes down which sounds it hears in both recordings. Comparing that with what our rules ask for gives a &lt;em&gt;phone error rate&lt;/em&gt;: the lower, the closer to the rules.&lt;/li&gt;
&lt;li&gt;Both melodies are drawn on top of each other, in semitones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two caveats. The recogniser is not perfect; for example, it hears many open &lt;em&gt;e&lt;/em&gt; sounds as closed ones. And Tolkien's tape is old and noisy. So the numbers are a guide, not a grade.&lt;/p&gt;

&lt;p&gt;Once Tolkien's recording was correctly cut into the lines of Namárië, I ran the script three times, each round fixing what the previous one showed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Round&lt;/th&gt;
&lt;th&gt;What changed&lt;/th&gt;
&lt;th&gt;Error rate (ours)&lt;/th&gt;
&lt;th&gt;Length vs Tolkien&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;first comparison&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;0.86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;slower, flatter melody&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;silent pause before the first word&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tolkien's own recording scores 33% against our rules, but listening to it, you can tell that the recogniser misses some of his phonemes. Manual corrections could probably improve that rate, but the purpose of this exercise was to find big pronunciation gaps to fix in Annie's code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqj6u8glxrepybzqlyvfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqj6u8glxrepybzqlyvfv.png" alt="Line 1, Ai! laurië lantar lassi súrinen: spectrograms of Tolkien and of Quenya-TTS, and both melodies" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The rules agree with Tolkien.&lt;/strong&gt; Where the recogniser hears Tolkien clearly, he says what the rules predict. The one interesting difference is &lt;em&gt;ly&lt;/em&gt;, as in &lt;em&gt;ilyë&lt;/em&gt; and &lt;em&gt;hiruvalyë&lt;/em&gt;: the rules use a single sound, but in Tolkien's voice the recogniser hears two phonemes, &lt;em&gt;l&lt;/em&gt; followed by &lt;em&gt;y&lt;/em&gt;. The sources leave both readings open (in Tengwar, &lt;em&gt;ly&lt;/em&gt; is one letter with two dots below), so I kept the single sound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The remaining errors are probably the model's.&lt;/strong&gt; I don't have access to Annie's trained model, and the few Quenya recordings available aren't enough to train a new one (more on that below). The main remaining issues are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model can't make the soft &lt;em&gt;l&lt;/em&gt;, so &lt;em&gt;ilyë&lt;/em&gt; comes out as "ihe";&lt;/li&gt;
&lt;li&gt;a final &lt;em&gt;r&lt;/em&gt; is weak and gets heard as &lt;em&gt;l&lt;/em&gt; (&lt;em&gt;Valimar&lt;/em&gt;, &lt;em&gt;tellumar&lt;/em&gt;), where it should be trilled;&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;d&lt;/em&gt; of &lt;em&gt;undulávë&lt;/em&gt; drifts toward &lt;em&gt;b&lt;/em&gt;, between two rounded &lt;em&gt;u&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti3rls42bh93gr81eh9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti3rls42bh93gr81eh9l.png" alt="Line 16, Namárië! Nai hiruvalyë Valimar: the soft l of hiruvalyë is where the model struggles most" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Not enough voice
&lt;/h2&gt;

&lt;p&gt;To see how far existing recordings could take the model, I fine-tuned Toucan on Kaggle with about eight minutes of Quenya readings, for private testing only. After 5,000 training steps, the model sounded better on the sentences it had heard and worse on the ones it hadn't: a textbook case of overfitting. Better rules can't fix that. What the model needs is hours of clean Quenya, ideally from a single voice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My own voice first.&lt;/strong&gt; I wrote a recording script of 2,500 lines, about 90 minutes of speech split into 25 sessions: first the lines of lesson 1 of &lt;a href="https://ma-quetilye-quenya.tolkien.lu/" rel="noopener noreferrer"&gt;&lt;em&gt;Ma Quetilyë Quenya?&lt;/em&gt;&lt;/a&gt;, then attested phrases, then sentences built from words attested in Eldamo. I record it at home with a small tool that can also play the synthetic version of each line. The same recogniser I used on Tolkien now checks my own takes, and it is humbling: it catches me on &lt;em&gt;nw&lt;/em&gt;, &lt;em&gt;ny&lt;/em&gt;, &lt;em&gt;au&lt;/em&gt; and an initial &lt;em&gt;qu&lt;/em&gt; more often than I would like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then volunteers.&lt;/strong&gt; A small website is almost ready, where volunteers can read sessions of the same script in their browser. A reviewer listens to each session and accepts it, or asks for single lines to be read again. A voice is used for training only once it has enough accepted audio.&lt;/p&gt;

&lt;p&gt;I changed my mind about one thing since I first wrote this post: the recordings will &lt;strong&gt;not&lt;/strong&gt; be published under an open licence. They are used only to train the model. Every volunteer can download their own recordings and do whatever they like with them, and can export or erase their data at any time, as the GDPR requires. The website, the storage and the training all run on servers in the European Union, in Luxembourg and Portugal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A voice that is nobody's.&lt;/strong&gt; A model trained on someone's voice will sound like them. So the model I publish will speak with a blended voice that belongs to no single volunteer. Every audio file it produces will carry an inaudible watermark marking it as synthetic, as the EU AI Act asks, and its licence will forbid using it to imitate a real person.&lt;/p&gt;

&lt;p&gt;If you would like to lend your voice, see the &lt;a href="https://github.com/brunopacheco1/ma-quetilye-quenya/issues/38" rel="noopener noreferrer"&gt;volunteer voices issue&lt;/a&gt;. I will write a follow-up post once the model has learned to speak with my voice.&lt;/p&gt;

&lt;p&gt;Please let me know if you have any feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namárië | See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tts</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Installing FreeBSD on a HP 250 G9</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Mon, 28 Oct 2024 21:36:50 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/installing-freebsd-on-a-hp-250-g9-3c10</link>
      <guid>https://dev.to/brunopacheco1/installing-freebsd-on-a-hp-250-g9-3c10</guid>
      <description>&lt;p&gt;I’ve acquired a Hewlett-Packard 250 15.6-inch G9 laptop with an Intel 12th Gen Core i3-1215U, 8GB of RAM, and a 500GB SSD. Not impressive hardware, I admit, but I will not run a lot of things locally anyway. So, let’s go directly to the point.&lt;/p&gt;

&lt;p&gt;I have installed FreeBSD 14.1. Actually, it is not as daunting as it may sound. The installation process is straightforward and easy to understand. The most complex thing would be partitioning, but in my case, I used the entire disk—no need for dual boot—I let the tool do its job alone.&lt;/p&gt;

&lt;p&gt;I will not add screenshots of each step, as much more experienced people have thoroughly covered it. Please refer to &lt;a href="https://www.youtube.com/watch?v=bQKaNbarQKI" rel="noopener noreferrer"&gt;this video&lt;/a&gt;, from &lt;a href="https://www.youtube.com/@RoboNuggie" rel="noopener noreferrer"&gt;RoboNuggie&lt;/a&gt;.  Approximately 70% of everything I needed to do is explained in that video.&lt;/p&gt;

&lt;p&gt;I will list below what was not covered in that video, like playing protected media, graphics, audio, and WiFi configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video
&lt;/h2&gt;

&lt;p&gt;This laptop has the Alder Lake-UP3 GT1 [UHD Graphics], which requires &lt;code&gt;drm-61-kmod&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd /usr/ports/graphics/drm-61-kmod
$ sudo make install clean
$ sudo sysrc kld_list+=i915kms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Windows Manager
&lt;/h2&gt;

&lt;p&gt;I am using Mate; for that, I simply followed the &lt;a href="https://docs.freebsd.org/en/books/handbook/desktop/" rel="noopener noreferrer"&gt;FreeBSD Handbook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chromium and CDM
&lt;/h2&gt;

&lt;p&gt;I found a couple of tutorials online, but here I will share simpler steps for supporting protected media playback—Netflix, Spotify, etc.—in Chromium:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Chromium and &lt;a href="https://www.freshports.org/www/foreign-cdm" rel="noopener noreferrer"&gt;Foreign CDM&lt;/a&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo pkg install foreign-cdm chromium
$ sudo sysrc linux_enable="YES"
$ sudo service linux start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Download ports and FreeBSD source code to compile &lt;code&gt;www/linux-widevine-cdm&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo pkg update
$ sudo pkg install portsnap git
$ sudo portsnap fetch
$ sudo portsnap extract
$ sudo git clone -b releng/14.1 https://git.freebsd.org/src.git /usr/src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://www.freshports.org/www/linux-widevine-cdm/?branch=2023Q4" rel="noopener noreferrer"&gt;Linux Widevine CDM&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd /usr/ports/www/linux-widevine-cdm
$ sudo make install clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Open &lt;code&gt;chrome://flags&lt;/code&gt; in Chromium and enable &lt;code&gt;Cdm Storage Database&lt;/code&gt; and &lt;code&gt;Cdm Storage Database Migration&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;Close Chromium and open again.&lt;/li&gt;
&lt;li&gt;You can test if DRM is enabled using &lt;a href="https://bitmovin.com/demos/drm" rel="noopener noreferrer"&gt;https://bitmovin.com/demos/drm&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Printer
&lt;/h2&gt;

&lt;p&gt;I have an HP OfficeJet, and from past Linux experience, I knew about &lt;a href="https://developers.hp.com/hp-linux-imaging-and-printing" rel="noopener noreferrer"&gt;HPLIP&lt;/a&gt;. Fortunately, it was also available as a binary to be installed with &lt;code&gt;pkg install hplip&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio
&lt;/h2&gt;

&lt;p&gt;Audio configuration is also simple. I needed to load a driver and configure hot-swap from speakers to headphones and vice versa whenever a new device was (un)plugged into the audio jack. I followed &lt;a href="https://freebsdfoundation.org/resource/audio-on-freebsd-quick-guide/" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wifi
&lt;/h2&gt;

&lt;p&gt;This was the most difficult part of the configuration. My first attempt was loading the driver compatible with my card. Apparently, the chipset was supported, and it worked at first. But as soon as I started consuming videos, it proved unstable. I tried a WiFi micro adapter, but it was quite slow. After a lot of reading on forums and some headaches, I discovered the &lt;a href="https://github.com/pgj/freebsd-wifibox" rel="noopener noreferrer"&gt;Wifibox project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s a clever idea of running WiFi drivers inside a Linux guest VM and bridging the connection back to the host machine. The footprint is quite small, but I can use the full potential of my WiFi. I followed &lt;a href="https://xyinn.org/md/freebsd/wifibox" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not tested components
&lt;/h2&gt;

&lt;p&gt;I haven’t tested Bluetooth or HDMI audio output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tested platforms:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zoom&lt;/li&gt;
&lt;li&gt;Webex&lt;/li&gt;
&lt;li&gt;GeForce Now&lt;/li&gt;
&lt;li&gt;Netflix&lt;/li&gt;
&lt;li&gt;Amazon Primevideo&lt;/li&gt;
&lt;li&gt;Spotify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well, that is it  with some manual steps, I have a operating system that is fit to my needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt | Ciao&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>freebsd</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Say hello to FreeBSD</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Thu, 17 Oct 2024 19:57:14 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/say-hello-to-freebsd-71p</link>
      <guid>https://dev.to/brunopacheco1/say-hello-to-freebsd-71p</guid>
      <description>&lt;p&gt;Hello, Bestie!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3garp90wdf8o00ebzlmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3garp90wdf8o00ebzlmd.png" alt="Bestie" width="400" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started the hobby of testing operating systems somewhere between 2006 and 2009. However, like many of you, I relied solely on social media to document my experiences. Unfortunately, much like the Library of Alexandria, Orkut is lost — though not necessarily as significant in terms of historical relevance.&lt;/p&gt;

&lt;p&gt;Back then, I experimented tons of different Linux distros, including a Brazilian one called &lt;a href="https://archiveos.org/kurumin/" rel="noopener noreferrer"&gt;Kurumin Linux&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I also explored some unconventional systems, like &lt;a href="https://archiveos.org/beos/" rel="noopener noreferrer"&gt;BeOS&lt;/a&gt;, &lt;a href="https://archiveos.org/opensolaris/" rel="noopener noreferrer"&gt;OpenSolaris&lt;/a&gt;, &lt;a href="https://archiveos.org/opendarwin/" rel="noopener noreferrer"&gt;OpenDarwin&lt;/a&gt;, and others I can no longer recall. VirtualBox was a lifesaver when hardware support was lacking — unsupported hardware is always the ultimate barrier — but whenever possible, I tested systems directly on my machine.&lt;/p&gt;

&lt;p&gt;I remember spending hours downloading images and burning them onto CDs. My desktop was decent but still slow and limited, so installations could take up to an hour, not to mention the configuration and updates. Any mistake meant starting over from scratch. Actually, my first Debian experience came from a CD I requested online, which I received a few weeks later. I can’t fully express the mix of emotions I felt with each attempt: exhaustion, joy, frustration, accomplishment, anger, happiness, and even occasional anxiety.&lt;/p&gt;

&lt;p&gt;All of this is just to illustrate how challenging it could be, and FreeBSD was no exception. I managed to install it after a couple of attempts. Everything needed to be compiled locally, including Java! It was the only time I ever had the opportunity to recompile a kernel!&lt;/p&gt;

&lt;p&gt;The result was an exceptionally fast and lightweight operating system. I used it for a while, but the challenges of version upgrades and limited compatibility with browser technologies of that time (e.g., Silverlight and Adobe Flash) eventually pushed me back to Linux.&lt;/p&gt;

&lt;p&gt;In 2015, I bought my first laptop, and that hobby came to an abrupt halt. Laptops often come with hardware specifically tailored to the software they ship with, sometimes even locked down. After a while, I just accepted using Windows with the Linux Subsystem. Everything worked as expected, and I gradually forgot the thrill of testing new operating systems.&lt;/p&gt;

&lt;p&gt;My old lad survived nearly 10 years with just a couple of upgrades — extra RAM and replacing the HDD with an SSD — but it's no longer functional, or perhaps I just didn't want to try fixing it. I immediately disassembled it to show my daughter what it looked like inside. I was time to move on.&lt;/p&gt;

&lt;p&gt;While searching for a new laptop, all those memories came flooding back. I decided then to buy relatively older hardware (released in 2022), without an operating system or any warranty restrictions. I wanted freedom.&lt;/p&gt;

&lt;p&gt;Now it's 2024, and the FreeBSD ecosystem has evolved for the better. Ports and compilation are still necessary for things like certain drivers and DRM-protected content (e.g., Netflix and Spotify), but they are no longer the default or the only option available. It takes just 5 minutes to install the entire OS, followed by several extra hours to configure every possible aspect — from screen brightness adjustments and automatic switching when headphones are detected, to automounting USB drives and optimizing CPU frequency.&lt;/p&gt;

&lt;p&gt;The result is once more fantastic, a fast, reliable and secure operating system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd7kulsuz393pvnqrb7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd7kulsuz393pvnqrb7q.png" alt="System monitor" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P.S.: I will follow this post with instructions on how to install FreeBSD 14.1 on my hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt | Ciao&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>freebsd</category>
    </item>
    <item>
      <title>My Journey to Open Data</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Tue, 04 Jul 2023 21:02:28 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/my-jorney-to-open-data-14o5</link>
      <guid>https://dev.to/brunopacheco1/my-jorney-to-open-data-14o5</guid>
      <description>&lt;p&gt;Well, this is not exactly a technical article. This post describes the end of a quest, and the beginning of a new journey: my first step towards Open Data, a world of fairy, strange people willing to change the world.&lt;/p&gt;

&lt;p&gt;My first contact with that world happened two years ago when I started learning Luxembourgish. Quickly, I discovered the lack of online content, community support, and learning tools. Then I decided to use my superpowers to build an application called &lt;a href="https://data.public.lu/fr/reuses/lod-to-anki/" rel="noopener noreferrer"&gt;LOD to Anki&lt;/a&gt;. This software aims to export the words from &lt;a href="https://lod.lu/" rel="noopener noreferrer"&gt;LOD.lu&lt;/a&gt; into &lt;a href="https://apps.ankiweb.net/" rel="noopener noreferrer"&gt;Anki&lt;/a&gt; flash cards. Please follow the links to learn more about them.&lt;/p&gt;

&lt;p&gt;Unfortunately, scraping the LOD's content proved to be an inefficient strategy. The HTML structure of the old website was cumbersome, making web crawling complex and slow. How would I keep my dataset updated? How would I ensure the performance of my tool? There was only one answer to those questions. I decided to ask for the data directly.&lt;/p&gt;

&lt;p&gt;To my surprise, the &lt;a href="https://portal.education.lu/zls" rel="noopener noreferrer"&gt;Zenter fir d'Lëtzebuerger Sprooch (ZLS)&lt;/a&gt; answered back. They shared the link to &lt;a href="https://data.public.lu/fr/organizations/zenter-fir-dletzebuerger-sprooch/" rel="noopener noreferrer"&gt;LOD's dataset&lt;/a&gt;, exposed on the &lt;a href="https://data.public.lu/" rel="noopener noreferrer"&gt;Luxembourgish Open Data Platform&lt;/a&gt;, and I marveled!&lt;/p&gt;

&lt;p&gt;With the dataset in hand, I rapidly developed my project. It was easy to integrate into the software, giving me time to focus on the Anki deck development instead. Thanks to that, all the important words listed by &lt;a href="https://sdl.inll.lu/" rel="noopener noreferrer"&gt;Schwätzt Dir Lëtzebuergesch&lt;/a&gt; books series are now documented and shared on &lt;a href="https://github.com/brunopacheco1/lod-anki/tree/main/decks/schwatzt_dir_letzebuergesh" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, for anyone to use and learn.&lt;/p&gt;

&lt;p&gt;That innocent interaction with ZLS and their dataset helped me make a small but positive impact on society. A few people expressed their gratitude for the software. Moreover, the LOD.lu's audio content was size reduced, with unnoticed quality loss, because I shared &lt;a href="https://github.com/nerg4l" rel="noopener noreferrer"&gt;László&lt;/a&gt;'s suggestion on audio compression with ZLS, potentially reducing their network traffic.&lt;/p&gt;

&lt;p&gt;Indeed, the planet is round, rotating, and revolving. Two years later, I seized the opportunity to join the fairy people. I am now working for the &lt;a href="https://www.linkedin.com/company/luxembourg-national-data-service-lnds/" rel="noopener noreferrer"&gt;Luxembourg National Data Service (LNDS)&lt;/a&gt;. I will be one of the many human beings working behind the curtain, integrating and providing solutions to support research and innovation through data.&lt;/p&gt;

&lt;p&gt;I would like to express my appreciation to all the people at &lt;a href="https://www.linkedin.com/company/pictet-technologies-sa/" rel="noopener noreferrer"&gt;Pictet Technologies&lt;/a&gt;. These four years of learning and growth were undoubtedly decisive in my career success.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The secret of long-lasting code</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Sun, 05 Feb 2023 02:30:44 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/the-secret-of-long-lasting-code-337n</link>
      <guid>https://dev.to/brunopacheco1/the-secret-of-long-lasting-code-337n</guid>
      <description>&lt;p&gt;I cannot talk about every software developer in the world, but I imagine that most of us had to deal with legacy applications at some point in our careers.&lt;/p&gt;

&lt;p&gt;But, before diving into it, we should find a definition of legacy software, and &lt;a href="https://www.gartner.com/en/information-technology/glossary/legacy-application-or-system" rel="noopener noreferrer"&gt;Gartner&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An information system that may be based on outdated technologies, but is critical to day-to-day operations. Replacing legacy applications and systems with systems based on new and different technologies is one of the information systems (IS) professionals’ most significant challenges. As enterprises upgrade or change their technologies, they must ensure compatibility with old systems and data formats that are still in use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let us review that definition, to understand the assumptions and reasons for software to be considered legacy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Software based on outdated technologies, but critical to day-to-day operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For me, this is somewhat of a paradox. The software should not use outdated technologies if it is critical to day-to-day operations. So, either both day-to-day operations and the application are old, so the whole thing is old and must be reviewed, or the software took too much time to be developed, so it does not fulfill the requirements anymore.&lt;/p&gt;

&lt;p&gt;In the end, this is about how often you review the software requirements. Keep questioning if the way you work is still correct. Or if the technology is still a good fit for your problem. No one should expect that a particular solution will last forever.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replacing legacy applications and systems with systems based on new and different technologies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my viewpoint, Replacing a system represents the end of the line. It means that someone made a terrible decision of not investing time and money to keep the process fine and tuned. Or someone made a wrong technical/architectural choice. Or someone poorly analyzed the requirements. Ultimately, it is too late to fix that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As enterprises upgrade or change their technologies, they must ensure compatibility with old systems and data formats that are still in use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, that is not surprising and is not a problem for legacy applications only. No matter the size of the application or the technology you use, integration is always a challenge. It is more of a coordination issue and requires regular attention. And it can break at any minute, in unexpected places.&lt;/p&gt;

&lt;p&gt;Summing up, &lt;strong&gt;Bad decision-making is solely the root cause of a legacy system&lt;/strong&gt;. The software is not legacy. The legacy (or the burden) is the mindset that kept the software as it was until it became a legacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Refactoring / Upgrading
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From a business perspective
&lt;/h3&gt;

&lt;p&gt;The requirements evolve and require regular ongoing review, often needing refactoring to align the code with these changes. That may add overhead to the business analyst, as the new feature needs to be compatible with existing ones. And refactoring may also increase the risk of introducing defects.&lt;/p&gt;

&lt;p&gt;But on the other hand, when we choose a shorter path, to avoid needed enhancements, we may postpone fixes to existing bugs, or increase technical debts, which is also dangerous to the project.&lt;/p&gt;

&lt;p&gt;So, to business stakeholders, refactoring is an investment. Well-written stories and well-defined test scenarios will guide the developers in the right direction, and BDD and E2E tests are great tools to detect regressions, so don't be afraid of the new.&lt;/p&gt;

&lt;h3&gt;
  
  
  From the development perspective
&lt;/h3&gt;

&lt;p&gt;Writing code is challenging. Enhancing existing code can be double challenging, depending on how far you are from the new requirements. Touching someone's code can be even dangerous, as you may not have that person to guide you. All this will make any developer anxious.&lt;/p&gt;

&lt;p&gt;Regular coders will stay in their comfort zone and keep things as they are. Outstanding engineers will take the task but may fail on the first try. However, with enough perseverance, they will excel.&lt;/p&gt;

&lt;p&gt;For me, refactoring is about rethinking and learning. It keeps all senior and junior developers up to date about the technology and the business. The more refactoring you do, the simpler and faster you will develop new features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Risks of not continually improving your code
&lt;/h3&gt;

&lt;p&gt;Well, your technical debt increases. And technical debt is a concept that refers to the cost of maintaining a software system due to quick and inefficient solutions to problems. It represents the trade-off between short-term development speed and long-term technical excellence. For more details, please follow these links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/TechnicalDebt.html" rel="noopener noreferrer"&gt;https://martinfowler.com/bliki/TechnicalDebt.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/agile/software-development/technical-debt" rel="noopener noreferrer"&gt;https://www.atlassian.com/agile/software-development/technical-debt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits of doing so
&lt;/h3&gt;

&lt;p&gt;Refactoring/upgrading is not only rewriting code/replacing technology with something else. It is all about stability. Tackling technical debt strengthens maintainability, thread safety, security, scalability, and low footprint. All these are crucial factors for a long-lasting project.&lt;/p&gt;

&lt;p&gt;But apart from that, keeping a stable project up and running brings satisfaction, visibility, and recognition. Nothing is more satisfying than a well-done job. And you and your project will become a reference for peers and stakeholders.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do then
&lt;/h3&gt;

&lt;p&gt;Each project is unique, but there are some steps to follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate your situation and prepare a plan. You want to know where you are and what to do, before starting the journey;&lt;/li&gt;
&lt;li&gt;Break it down into small pieces. It will be easier to get accepted and validated. You can start with single components or fix duplicated code. &lt;a href="https://www.oreilly.com/library/view/clean-code-a/9780136083238/" rel="noopener noreferrer"&gt;Clean Code&lt;/a&gt; provides you with many insights;&lt;/li&gt;
&lt;li&gt;Make sure you have good test coverage, it will support you on avoiding regressions. If the test coverage is not enough, &lt;a href="https://www.agilealliance.org/glossary/tdd" rel="noopener noreferrer"&gt;TDD (Test-driven Development)&lt;/a&gt; is a good thing to start practicing here;&lt;/li&gt;
&lt;li&gt;Improve your app monitoring and traceability. Not just to prepare for possible new bugs, but also to look at your current situation. For monitoring, I suggest &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;, For traceability on Spring Boot, &lt;a href="https://spring.io/projects/spring-cloud-sleuth" rel="noopener noreferrer"&gt;Sleuth&lt;/a&gt; is the easiest choice. Please, read this &lt;a href="https://medium.com/javarevisited/distributed-tracing-in-microservices-spring-boot-125272b58ad8" rel="noopener noreferrer"&gt;article&lt;/a&gt; understand the concept of Distributed Tracing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apart from that, there is not much to do than doing your job as it is supposed to be. Excellent coding principles, clean code, meaningful tests, code review, pair programming, Sonar, intelligent IDEs, AI... Use every weapon you may have to ensure the code you write is well written and well covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The golden rule here is: never to stop trying new things, or you are stuck in the past. And most probably, everyone else will waste a lot of time and money doing the same thing you did over and over. At some point, your software will become a legacy system, and everyone will complain. Eventually, it will get replaced.&lt;/p&gt;

&lt;p&gt;So, please do not be afraid of changing the code. No piece of code is generic enough to cover all possibilities, nor so well written that is the best solution for the requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>programming</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Optimizing Decision Making with a Trie Tree-Based Rules Engine: An Experience Report</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Tue, 24 Jan 2023 01:41:09 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/optimizing-decision-making-with-a-trie-tree-based-rules-engine-an-experience-report-3p90</link>
      <guid>https://dev.to/brunopacheco1/optimizing-decision-making-with-a-trie-tree-based-rules-engine-an-experience-report-3p90</guid>
      <description>&lt;p&gt;In Pictet Technologies, my team relies a lot on decision models. These models allow our business analysts to input Compliance business rules directly into the systems with minimal developer intervention. When I joined the company, we used to use both &lt;a href="https://www.kie.org/" rel="noopener noreferrer"&gt;Drools&lt;/a&gt; and &lt;a href="https://camunda.com/" rel="noopener noreferrer"&gt;Camunda&lt;/a&gt;. However, we faced severe memory and performance issues, specifically with Camunda, prompting me to explore alternatives.&lt;/p&gt;

&lt;p&gt;For those not familiar with these solutions, both are rules engine providers. And a rules engine is a software system designed to process and execute sets of business rules. These rules provide automated decisions based on a set of inputs. In a nutshell, a rules engine is a very sophisticated if/else interpreter. For more details, I suggest you follow this &lt;a href="http://www.mastertheboss.com/bpm/drools/what-is-a-rule-engine/" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first challenge I encountered was with the decision model and notation file (or DMN). By the way, DMN is the “standard” format that both Drools and Camunda implement. While it is relatively easy to understand, it is impossible to load the same file on both vendors. Another point is that most of our cases are simple decision tables. If it were not for the number of rules, it would be simpler to code the business rules in Java directly.&lt;/p&gt;

&lt;p&gt;But the most critical issue related to these engines was performance. Our DMN-based services struggled to handle concurrent usage. Even on single requests, the process took longer than our clients were expecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the journey
&lt;/h2&gt;

&lt;p&gt;Initially, I evaluated alternative solutions, but ultimately, Drools and Camunda were the dominant players in the field when I started these studies. And both were and still are easy to use and integrate with, free to use, and offer tools for maintaining the DMN files.&lt;/p&gt;

&lt;p&gt;The next step was using a database, where the rules could be stored and easily accessed through queries. However, this approach quickly resulted in poor performance.&lt;/p&gt;

&lt;p&gt;Eventually, I began exploring the possibility of developing my solution, which prompted me to conduct research. That was when I discovered the Trie Tree, a rather basic data structure that arranges words in shared prefixes. Please, follow this &lt;a href="https://www.toptal.com/java/the-trie-a-neglected-data-structure" rel="noopener noreferrer"&gt;link&lt;/a&gt; for details on how Trie Tree works. There is also this &lt;a href="https://www.cs.usfca.edu/~galles/visualization/Trie.html" rel="noopener noreferrer"&gt;visualization tool&lt;/a&gt; to get acquittance with this data structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o2x00rh2uympgqu9orj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o2x00rh2uympgqu9orj.png" alt="Trie Tree representation" width="542" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Image reused from &lt;a href="https://www.geeksforgeeks.org/trie-insert-and-search/" rel="noopener noreferrer"&gt;GeeksForGeeks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon closer examination, I found that treating each input of a rule as a character in a string, Trie Tree is a good solution for a rules engine. The challenge when I started developing it was the “ANY” value, which allows any input value for a given entry. To accommodate this, it was necessary to accurately propagate the children of the ANY node to all siblings, and vice versa, ensuring the correct branch may be traversed.&lt;/p&gt;

&lt;p&gt;Another challenge of implementing the Trie Tree was the multiple rules triggering implemented by both vendors. To achieve it using Trie, the leaf node should contain a sorted collection of rule IDs - the sorting mechanism depends on the Hit Policy selected by the developer. In most of our cases, we use the FIRST hit policy, which retrieves the first element of the collection sorted by rule id.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing the solution
&lt;/h2&gt;

&lt;p&gt;The overall performance was exceptional, as demonstrated later. However, memory consumption remained high. After studying other Trie Tree implementations - &lt;a href="https://www.cs.usfca.edu/~galles/visualization/RadixTree.html" rel="noopener noreferrer"&gt;Radix Tree&lt;/a&gt; is an example - I developed an algorithm to eliminate ANY nodes without siblings, as it is always the default branch. This approach reduced some subtrees to just one node.&lt;/p&gt;

&lt;p&gt;After further testing, I realized that the number of nodes is significantly smaller if I ordered the inputs appropriately. That is a computationally complex problem, but luckily, there is a simple approach to avoid testing all possible scenarios. One should sort the input entries based on the number of ANY values they have. In one particular service, this technique allowed us to reduce the number of nodes from 30k to 1.5k. &lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing this article
&lt;/h2&gt;

&lt;p&gt;First, I transformed this Trie Tree implementation into an open-source project, which I called &lt;a href="https://github.com/brunopacheco1/praecepta" rel="noopener noreferrer"&gt;Praecepta&lt;/a&gt; (Rules in Latin), so anyone can try it out. Then, I added three modules to it, with a decision table based on a real scenario (blurred input/output values). Each module uses a different framework: Praecepta, Camunda 7, and &lt;a href="https://kogito.kie.org/" rel="noopener noreferrer"&gt;Kogito&lt;/a&gt; (the newest Kie solution for the Cloud).&lt;/p&gt;

&lt;p&gt;I used SpringBoot 3.0.2, GraalVM 22 (JVM mode), a MacOS 2,6 GHz 6-Core Intel Core i7, running 1000 users for 5 minutes. The idea was to test how memory consumption and CPU usage evolve. Below, I compared the footprint of these three solutions. I collected the total count of requests, throughput, memory consumption, and CPU usage using &lt;a href="https://visualvm.github.io/" rel="noopener noreferrer"&gt;VisualVM&lt;/a&gt; and &lt;a href="https://gatling.io/" rel="noopener noreferrer"&gt;Gatling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beating Kogito and Camunda
&lt;/h2&gt;

&lt;p&gt;The numbers do not lie!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Requests (total count)&lt;/th&gt;
&lt;th&gt;Throughput (mean count/s)&lt;/th&gt;
&lt;th&gt;Memory (Peak)&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Praecepta&lt;/td&gt;
&lt;td&gt;6.614.663&lt;/td&gt;
&lt;td&gt;21.337&lt;/td&gt;
&lt;td&gt;~100MB&lt;/td&gt;
&lt;td&gt;~25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kogito&lt;/td&gt;
&lt;td&gt;3.615.549&lt;/td&gt;
&lt;td&gt;11.663&lt;/td&gt;
&lt;td&gt;~225MB&lt;/td&gt;
&lt;td&gt;~30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camunda 7&lt;/td&gt;
&lt;td&gt;1.238.667&lt;/td&gt;
&lt;td&gt;3.995&lt;/td&gt;
&lt;td&gt;~450MB&lt;/td&gt;
&lt;td&gt;~35%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;You can see the results below.&lt;/p&gt;

&lt;p&gt;Sadly Camunda 7 did not perform well. It was 3x slower and 2x bigger than Kogito. The last proved acceptable given the numbers, but still, it had twice as much memory as Praecepta. Praecepta, on the other hand, steadily kept memory below 100MB, with low CPU usage and 2x greater throughput, compared to the second place.&lt;/p&gt;

&lt;p&gt;The outcome is not that Kogito nor Camunda sucks. It always depends on what we plan and want in our system. If you have a much more complex scenario, Kogito or Camunda may fit better to your needs, but first, you should evaluate other solutions before buying/using one of them.&lt;/p&gt;

&lt;p&gt;For our team, a simple data structure had an immensely positive impact. We could provide microservices with a smaller footprint and much higher availability.&lt;/p&gt;

&lt;p&gt;For me, It was an excellent opportunity to use my computer science background and learn new things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Praecepta - TrieTree
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foah5lifqrq8no5rg3a8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foah5lifqrq8no5rg3a8j.png" alt="Trie Tree gatling" width="710" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gmewte8olthyokqvh0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gmewte8olthyokqvh0w.png" alt="Trie Tree jvm" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Kogito
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxexop75yuqwnf25pyjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxexop75yuqwnf25pyjm.png" alt="Kogito gatling" width="712" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyi26ip3l3egmrximx6x4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyi26ip3l3egmrximx6x4.png" alt="Kogito jvm" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Camunda
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpsexk8z75ggudanzi7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpsexk8z75ggudanzi7y.png" alt="Camunda gatling" width="702" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7jbo2c9hmuyfjc7dt74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7jbo2c9hmuyfjc7dt74.png" alt="Camudna jvm" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>gratitude</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Staying Sharp in Software Engineering</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Sat, 07 Jan 2023 21:15:24 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/staying-sharp-in-software-engineering-2pd</link>
      <guid>https://dev.to/brunopacheco1/staying-sharp-in-software-engineering-2pd</guid>
      <description>&lt;p&gt;When it comes to working, learning is often a key concern. That is especially true in software engineering, where there is always a way to do something faster, cheaper, or better, which often requires new technologies. In this field, it is also crucial to ensure high-quality code. That includes not only coding, but also dependency management, testing, code review, continuous integration and deployment, tracing, monitoring, and measuring.&lt;/p&gt;

&lt;p&gt;Fortunately, most of those tasks can be automated, allowing developers to focus on the actual coding. However, this is just the beginning of the journey to becoming a developer.&lt;/p&gt;

&lt;p&gt;Acquiring experience in software engineering is a long and tedious process that requires constant practice and learning. However, it is easy to get comfortable, relying on familiar ideas and solutions. After a year, you may know enough about how and where to do things, but after five years, all the projects may start to look the same. Finding new ways to challenge yourself and try new things is indispensable to avoid stagnation.&lt;/p&gt;

&lt;p&gt;I was talking to friends at work and this topic came to life. I will share what I often do to continue learning. Please, join me on this journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Online Coding Challenges
&lt;/h2&gt;

&lt;p&gt;One way to keep learning is by participating in online coding challenges. These challenges can be a fun and engaging way to learn, as they often have a game-like structure with small challenges that increase in difficulty as you progress.&lt;/p&gt;

&lt;p&gt;One benefit is that coding challenges can help you stay up-to-date with the latest technologies and programming languages. These platforms offer also challenges in different areas, such as algorithms, data structures, and design patterns.&lt;/p&gt;

&lt;p&gt;Another benefit is that coding challenges can help you develop problem-solving and critical-thinking skills. Many challenges require you to think creatively and bring solutions to complex problems, which can be a great way to exercise your brain and improve your overall problem-solving abilities.&lt;/p&gt;

&lt;p&gt;Coding challenges can also be a great way to build your portfolio and demonstrate your skills. Many platforms allow you to share your progress and accomplishments, and earning badges or ranking highly on leaderboards can be a great way to showcase your abilities to others.&lt;/p&gt;

&lt;p&gt;However, this approach can be time-consuming, as it often requires a significant investment of time and effort to finish. Additionally, the competitive nature of these platforms can be stressful for some people and may not be a good fit for everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a coding partner
&lt;/h2&gt;

&lt;p&gt;Another way to challenge yourself and learn new things is to work with a peer. Having someone alongside can help keep your motivation up and provide opportunities for feedback and review.&lt;/p&gt;

&lt;p&gt;There are several ways you can find a peer to learn software engineering with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Join online communities or forums related to software engineering. These communities often have members willing to mentor or collaborate with others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reach out to local meetup groups or attend industry events in your area. These events can be a great way to meet other software engineers and find potential peers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect with people in your professional network. Ask around to see if anyone is interested in collaborating or learning together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider joining a study group or starting one of your own. You can find other people interested in learning what you want to learn and meet regularly to study and learn together.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the key is to be proactive and put yourself out there. Don't be afraid to reach out to people and ask if they would be interested in learning together. That is a valuable way to deepen your understanding of a topic and gain new perspectives, but there are potential challenges.&lt;/p&gt;

&lt;p&gt;One challenge is time constraints. Depending on your schedules, usually, it is hard to find time to work together. We need to be flexible and work around each other's schedules.&lt;/p&gt;

&lt;p&gt;Motivation can also be a challenge when learning with a peer, especially when working on a difficult or complex topic. You must be proactive to keep each other motivated and on track.&lt;/p&gt;

&lt;p&gt;Finally, conflicts can arise when working with a peer. You should be open to feedback and find ways to resolve any disagreement that may emerge constructively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code for yourself
&lt;/h2&gt;

&lt;p&gt;The benefits of working on projects relevant to your interests or needs are clear goals and purpose. By that, you are more likely to stay engaged and motivated, which can help you learn more effectively.&lt;/p&gt;

&lt;p&gt;In addition to the learning opportunities, working on real-world problems can give you valuable experience that can be helpful when applying for jobs or working on professional projects. This hands-on experience can help you stand out in a competitive job market and give you confidence in your abilities.&lt;/p&gt;

&lt;p&gt;Finally, seeing a project through from start to finish is very rewarding. Developing a tool or application to solve a specific need can be a very fulfilling accomplishment, as you see the results of your work practically and tangibly.&lt;/p&gt;

&lt;p&gt;On the other hand, you may encounter different challenges, like time constraints. Balancing these projects with other commitments, such as work or family, can be quite difficult. Finding time to work on your projects can require careful planning and time management.&lt;/p&gt;

&lt;p&gt;Debugging and troubleshooting can also be more challenging when working on your own. Without the benefit of colleagues to collaborate with, you may have to spend more time figuring out problems and fixing errors.&lt;/p&gt;

&lt;p&gt;Finally, limited exposure can be challenging when working on projects for yourself. Without a team or organization behind you, it can be harder to get your work noticed and recognized by others. That can make it harder to build a portfolio or get feedback on your work.&lt;/p&gt;

&lt;h2&gt;
  
  
  To cut it short
&lt;/h2&gt;

&lt;p&gt;While stepping out of your comfort zone can be time-consuming and require a lot of effort, it will help you gain new perspectives and be very beneficial in the long run.&lt;/p&gt;

&lt;p&gt;It's imperative to seek out different resources, such as books, courses, real-world projects, or friends, to ensure that you are well-rounded and have a broad range of experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://brilliant.org/" rel="noopener noreferrer"&gt;Brilliant&lt;/a&gt; - Huge variety of content, from Quantum Mechanics to Computer Science&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw" rel="noopener noreferrer"&gt;3Blue1Brown&lt;/a&gt; - Youtube Channel to learn Linear Algebra, Calculus and more&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hackerrank.com" rel="noopener noreferrer"&gt;HackerRank&lt;/a&gt; - Very good online coding platform&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.kaggle.com/" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt; - Public datasets and competitions for Machine Learning&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://rocket.nergal.xyz/" rel="noopener noreferrer"&gt;NotRocketScience_&lt;/a&gt; - Technical challenges proposed by &lt;a href="https://github.com/nerg4l" rel="noopener noreferrer"&gt;László&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://googlecloud.qwiklabs.com/" rel="noopener noreferrer"&gt;Google Qwiklabs&lt;/a&gt; - Hands-on oriented web courses for Google Cloud Computing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.linkedin.com/learning" rel="noopener noreferrer"&gt;LinkedIn Learning&lt;/a&gt; - Web courses&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.coursera.org/" rel="noopener noreferrer"&gt;Coursera&lt;/a&gt; - More web courses&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.udemy.com/" rel="noopener noreferrer"&gt;Udemy&lt;/a&gt; - And more web courses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Covid led me to my first open-source contribution</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Wed, 04 Jan 2023 01:00:58 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/covid-led-me-to-my-first-open-source-contribution-42n</link>
      <guid>https://dev.to/brunopacheco1/covid-led-me-to-my-first-open-source-contribution-42n</guid>
      <description>&lt;p&gt;It's been a while since I've shared anything here. Mostly because my interests and priorities have changed. But now I'm back, and I hope to produce more content.&lt;/p&gt;

&lt;p&gt;Let's go back to 2020, when I stopped writing. The Covid era, baby!&lt;/p&gt;

&lt;p&gt;There were people going crazy because they couldn't see other people. There were people baking bread and brewing beer at home. And I was becoming a first-time parent, or at least starting the journey.&lt;/p&gt;

&lt;p&gt;I'm an engineer by love and a developer to the bone. Like everyone else, I was looking for something to focus my thoughts on. And an article about a COBOL system that was overloaded in the US caught my attention. Of course, I had heard of COBOL in university, but the most "hardcore" language I had briefly touched was C/C++.&lt;/p&gt;

&lt;p&gt;I was bored, and I didn't want to see more of REST, more of C-like languages, or more of architectural problems. I wanted something more basic, easy to start and easy to stop. Why not COBOL? I thought it might be useful in the future.&lt;/p&gt;

&lt;p&gt;So, like any developer would do, I started a web course. If you're interested, you can check it out on &lt;a href="https://www.linkedin.com/learning/learning-cobol-2015" rel="noopener noreferrer"&gt;LinkedIn Learning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What did I find out? COBOL is an elitist and segregating language. You have to have access to mainframes to code, and you have to have access to paid tools to code and debug. There is only one open-source compiler for COBOL, &lt;a href="https://gnucobol.sourceforge.io/" rel="noopener noreferrer"&gt;GnuCOBOL&lt;/a&gt;. And when I started my journey, there were zero open-source debuggers.&lt;/p&gt;

&lt;p&gt;I was totally disappointed. How could that be? COBOL is a f**** old language. So I started digging around forums and I found an IBM learning platform that was oriented towards COBOL. If I remember correctly, the students got access to mainframes. But one thing caught my attention: there was another frustrated person complaining about the lack of open-source tools to code on a regular computer. They shared that they were writing a &lt;a href="https://marketplace.visualstudio.com/items?itemName=OlegKunitsyn.gnucobol-debug" rel="noopener noreferrer"&gt;COBOL debugger&lt;/a&gt; for VSCode.&lt;/p&gt;

&lt;p&gt;I tried out that tool and I thought it was a great idea. But soon I realized it wasn't mature enough to debug even one of the simplest tasks I did in the web course I mentioned.&lt;/p&gt;

&lt;p&gt;Well, as we say in Rio, I had the cheese and the knife in my hands. I'm a developer, and I wanted to learn COBOL. So why not contribute to that tool? That little comment turned my course in a divergent direction. Soon I forgot about "how to read a file in COBOL" and started asking "how GnuCOBOL transpiles code into C", "how to debug C in the command line", and "how gdb integrates with VSCode."&lt;/p&gt;

&lt;p&gt;In the middle of the project, I was already in touch with GnuCOBOL developers, asking them for new compiler features to help debug float and binary data types supported by that compiler. I was also adding support for JavaScript expressions in the debugger inspection view. And in my latest contributions, I was working on debugging remote processes and SSH tunneling.&lt;/p&gt;

&lt;p&gt;All of this, all of those long nights coding, just because of a comment on a forum about COBOL.&lt;/p&gt;

&lt;p&gt;And you know what? It was an amazing experience. I learned a lot of things, perhaps nothing directly useful for my current daily duties, but at least I contributed to an open-source project, impacting the lives of thousands of other anonymous developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See you | Bis geschwënn | Até mais | À bientôt&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>cobol</category>
    </item>
    <item>
      <title>Quarkus, Kotlin and GraalVM</title>
      <dc:creator>Bruno Pacheco</dc:creator>
      <pubDate>Fri, 22 Nov 2019 19:48:13 +0000</pubDate>
      <link>https://dev.to/brunopacheco1/quarkus-kotlin-and-graalvm-35aa</link>
      <guid>https://dev.to/brunopacheco1/quarkus-kotlin-and-graalvm-35aa</guid>
      <description>&lt;p&gt;During a chat with &lt;a href="https://github.com/nerg4l" rel="noopener noreferrer"&gt;László&lt;/a&gt;, I've asked him for some ideas to practice Quarkus and GraalVM, he suggested to code the classic game Snake (AKA Worms by me :P).&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivations
&lt;/h3&gt;

&lt;p&gt;Why &lt;a href="https://quarkus.io" rel="noopener noreferrer"&gt;Quarkus&lt;/a&gt;? I've read an article saying that Quarkus is a great and promising solution for Java in the Cloud, as it drastically reduces the application's footprint and has an insanely fast startup when running on native code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.graalvm.org/" rel="noopener noreferrer"&gt;GraalVM&lt;/a&gt; is a general-purpose VM for running applications written in a bunch of different languages, allowing a polyglot code. At first glance, I felt it was an amazing thing to try, and so I did. In the end, this first trial wasn't fruitful, due to some strange bug on a node app I was coding at that time, so I just gave up for that moment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; has no reasonable arguments to be here except it is a brand-new Java dialect which I was already learning, but I have never used it on something substantial, so why not?&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting the adventure
&lt;/h3&gt;

&lt;p&gt;I've started the project just reading the guides on Quarkus.io, which are quite hands-on, but superficial. Quarkus is simple to use, as any recent framework for web development. So simple that in five minutes I had a rest service running locally... of course, an HTTP call returning a "Hello World" string doesn't deserve to be called a service, does it?&lt;/p&gt;

&lt;p&gt;So, to start your project from scratch, they have implemented a bootstrap command. Please refer to the followin command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=getting-started \
    -DclassName="org.acme.quickstart.GreetingResource" \
    -Dpath="/hello"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an easy way to add new dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mvn quarkus:add-extension -Dextensions="vertx"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving on, my first steps were: defining the domain, JPA, H2, a couple of CRUD endpoints and some DTOs. After having this square-shaped code, I decided to migrate it to Kotlin, and bugs started popping up as expected.&lt;/p&gt;

&lt;p&gt;The first problem which I had to face was JPA. It requires open classes and by default it isn't true for Kotlin. To fix this, there's a flag to enable JPA support during compilation. I don't like this kind of solution, but this is somewhat my fault as I wanted to use a mature Java library instead of using something more Kotlin-like.&lt;/p&gt;

&lt;p&gt;The second problem was the JSON payload serialization. I had some issues with JSON-B (the default option), and then I decided to try Jackson. Again, just for the sake of simplicity, as I was already testing tons of new things.&lt;/p&gt;

&lt;p&gt;It worked nicely and the DTOs were cleaner than before. However, another plugin had to be added to the Kotlin compiler (the no-arg plugin option) and a custom annotation to help the compiler identifying the DTOs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Dto
data class MatchMapPlayer(
        var playerId: Long,
        var status: MatchPlayerStatus = MatchPlayerStatus.PLAYING,
        var wormLength: Int = 0,
        var direction: Direction = Direction.DOWN,
        var position: List&amp;lt;MapPoint&amp;gt; = arrayListOf()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third problem was similar to the JPA issue. All managed beans have to be open. Can you guess the solution? Yes another Kotlin compiler flag.&lt;/p&gt;

&lt;p&gt;So, in the end, my Koltin compiler configuration looked like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;configuration&amp;gt;
  &amp;lt;compilerPlugins&amp;gt;
    &amp;lt;plugin&amp;gt;jpa&amp;lt;/plugin&amp;gt;
    &amp;lt;plugin&amp;gt;no-arg&amp;lt;/plugin&amp;gt;
    &amp;lt;plugin&amp;gt;all-open&amp;lt;/plugin&amp;gt;
  &amp;lt;/compilerPlugins&amp;gt;
  &amp;lt;pluginOptions&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.ws.rs.Path&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.enterprise.context.ApplicationScoped&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.enterprise.context.RequestScoped&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.ws.rs.ext.Provider&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.persistence.MappedSuperclass&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.persistence.Entity&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;all-open:annotation=javax.persistence.Embeddable&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;no-arg:annotation=com.dev.bruno.worms.dto.Dto&amp;lt;/option&amp;gt;
  &amp;lt;/pluginOptions&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've also created an abstract Repository class to have a common behavior for all entity repos (copying Spring), but I had a problem with the dependency injection in the superclass.&lt;/p&gt;

&lt;p&gt;I tried using constructor dependency injection, passing it from the child to the superclass using super, but it hasn't worked properly. The work around was injecting the &lt;code&gt;EntityManager&lt;/code&gt; directly in the field, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @PersistenceContext
    protected lateinit var em: EntityManager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I had to change all &lt;code&gt;@OnToMany&lt;/code&gt; from &lt;code&gt;List&lt;/code&gt;/&lt;code&gt;Set&lt;/code&gt; to &lt;code&gt;MutableList&lt;/code&gt;/&lt;code&gt;MutableSet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And finally, I don't remember why, but now all fields in the domain and DTO classes were &lt;code&gt;var&lt;/code&gt;, instead of &lt;code&gt;val&lt;/code&gt;. I have to check why again.&lt;/p&gt;

&lt;p&gt;I've also implemented some business code, which is not important to discuss here (but if you are interested, go to&lt;br&gt;
&lt;a href="https://github.com/brunopacheco1/worms/tree/master/src/main/kotlin/com/dev/bruno/worms/evaluation" rel="noopener noreferrer"&gt;this link&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  And GraalVM enters the scene...
&lt;/h3&gt;

&lt;p&gt;After trespassing the first barrier, I was ready for the next boss (the final one?): compiling the project to native code.&lt;/p&gt;

&lt;p&gt;According to Quarkus.io, this shouldn't be complicated as they aim to simplify native code compilation using their on maven plugin. Even their guides being simplistic, they are not lying. But they could have mentioned the Reflection limitation in GraalVM, which will be explained in the following lines.&lt;/p&gt;

&lt;p&gt;So, to build the app, you have to run a similar mvn command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mvn clean package -Pnative -Dnative-image.docker-build=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, I was building a containerized native code, that's why there's this docker-build arg. They also provide a dockerfile sample.&lt;/p&gt;

&lt;p&gt;I thought at this stage that would be enough, but no. After running the build and running the app on Docker, I faced exceptions on Jackson. So, reflection is not supported by default on native code and it took me a while to understand why, and a bit more to find a way to fix it.&lt;/p&gt;

&lt;p&gt;On a side note, the build consumes a lot of memory and CPU, almost killing my old Lenovo. :(&lt;/p&gt;

&lt;p&gt;So, GraalVM does allow you to use reflection, but for that, you need to add a config file, located at &lt;code&gt;META-INF/native-image/reflect-config.json&lt;/code&gt; (it can be in a different folder with a different name, but this is the default path).&lt;/p&gt;

&lt;p&gt;First I decided not to manually write this file, and after googling I've found this &lt;a href="https://link.medium.com/n2us7AWxF1" rel="noopener noreferrer"&gt;blog&lt;/a&gt; presenting an agent to do this job. The idea is to observe the JVM during runtime and generate the config.&lt;/p&gt;

&lt;p&gt;To do this, you simply have to run the app with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image your.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the app with this agent, the generated file was hugely polluted and it didn't fix the problem anyway, as the agent didn't add all needed classes. So, I ended up typing the file manually. And surprisingly, I only had to add the DTO classes.&lt;/p&gt;

&lt;p&gt;The file should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "name": "com.dev.bruno.worms.dto.ExceptionResponse",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true,
    "fields": [
      {
        "name": "message",
        "allowWrite": true
      }
    ]
  }
  ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;I do recognize the benefits of using Kotlin such as nullability, immutability, less verbose code, tons of new coder-friendly functions, however they are not that important when you have clean code and efficient testing.&lt;/p&gt;

&lt;p&gt;Also, I don't like polluted POM files, and it annoyed me having to add build plugins, compiler flags and configurations. I felt guilty about using JPA and Jackson though. Perhaps, if I had used something more Kotlin oriented, I wouldn't have had these issues. Let's see what happens in the next project.&lt;/p&gt;

&lt;p&gt;Quarkus has an insanely fast startup indeed, and the memory footprint is great. I have used a micro instance on Google Cloud to test the game, and it handled properly the load, without crashing anything. Quarkus is a bit immature yet, as it required some refactoring when I decided to move to newer versions. Nonetheless, it is promising, as it has huge support for different technologies and libraries. I'll use it again.&lt;/p&gt;

&lt;p&gt;I have loved GraalVM at first sight, as the wish of a polyglot code is in my mind for a while, and now it's becoming reality. In the end, I didn't use this feature, however, the native code generation fascinated me as well. I didn't like the way it consumes resources though. Compiling any other language code to native is not consuming 10% of what GraalVM has consumed from my machine (not judging...). I would wait for improvements on this area before using it on commercial solutions.&lt;/p&gt;

&lt;p&gt;To sum up: I have enjoyed playing with all these new technologies all together. Due to time and availability constraints, I couldn't investigate more. Perhaps improving and refactoring the code. The final solution is not bad and it has proven to me that Java definitely can be used in the cloud, it's just a matter of investment in new solutions, like Quarkus and GraalVM.&lt;/p&gt;

&lt;p&gt;Please, refer to the project &lt;a href="https://github.com/brunopacheco1/worms" rel="noopener noreferrer"&gt;repository&lt;/a&gt; to have a better picture of what I have coded. And feel free to comment out there.&lt;/p&gt;

</description>
      <category>graalvm</category>
      <category>kotlin</category>
      <category>quarkus</category>
    </item>
  </channel>
</rss>
