<?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>Forem: Christian Holländer</title>
    <description>The latest articles on Forem by Christian Holländer (@christian98).</description>
    <link>https://forem.com/christian98</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F345318%2F0d320a2b-dcc5-4bde-8481-80ec0be1d666.JPG</url>
      <title>Forem: Christian Holländer</title>
      <link>https://forem.com/christian98</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/christian98"/>
    <language>en</language>
    <item>
      <title>Choosing the Right Versioning Scheme: It's About Who Consumes Your Software</title>
      <dc:creator>Christian Holländer</dc:creator>
      <pubDate>Wed, 22 Apr 2026 10:38:39 +0000</pubDate>
      <link>https://forem.com/christian98/choosing-the-right-versioning-scheme-its-about-who-consumes-your-software-1i3</link>
      <guid>https://forem.com/christian98/choosing-the-right-versioning-scheme-its-about-who-consumes-your-software-1i3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted over on my personal website. Maybe you want to check it out &lt;a href="https://christian-hollaender.de/posts/8-choosing-the-right-versioning-scheme-its-about-who-consumes-your-software" rel="noopener noreferrer"&gt;[HERE]&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Version numbers communicate to their consumer. Use SemVer when code depends on your interface externally. Use CalVer for human-facing software. The question isn't app type — it's: human or code consumer?&lt;/p&gt;




&lt;p&gt;Every software project needs a version number. Yet the choice of &lt;em&gt;how&lt;/em&gt; to version is often made by habit ("we've always used SemVer") or by imitation ("npm does it, so we will too") rather than by deliberate reasoning. This leads to version numbers that are either misleading, meaningless, or unnecessarily complex.&lt;/p&gt;

&lt;p&gt;There is a cleaner mental model: &lt;strong&gt;your versioning scheme should match who consumes your software.&lt;/strong&gt; Humans or code. That single question cuts through most of the confusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Distinction: Human Consumers vs. Code Consumers
&lt;/h2&gt;

&lt;p&gt;When a human uses your software, they interact with it through a UI. They click buttons, read screens, fill forms. They don't &lt;em&gt;integrate against&lt;/em&gt; your software — they just use it.&lt;/p&gt;

&lt;p&gt;When code consumes your software — a library, an API, an SDK — it integrates against a specific contract: function signatures, endpoints, data shapes, CLI flags. A breaking change has a precise, technical meaning: something that worked before now doesn't compile, fails at runtime, or returns unexpected data.&lt;/p&gt;

&lt;p&gt;This distinction drives everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code consumers need SemVer&lt;/strong&gt; — because a version number is the primary communication channel about compatibility, and dependency resolvers literally parse it to make decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human consumers don't need SemVer&lt;/strong&gt; — because what does a MAJOR bump even mean to a user? That you changed the navigation? Dropped a feature? Added a dark mode? None of SemVer's three signals map cleanly onto the human experience of software.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Semantic Versioning: A Protocol for Dependency Graphs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://semver.org" rel="noopener noreferrer"&gt;SemVer&lt;/a&gt; uses the format &lt;code&gt;MAJOR.MINOR.PATCH&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MAJOR&lt;/strong&gt; — breaking changes; dependents may need to update their code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MINOR&lt;/strong&gt; — new features, backwards compatible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PATCH&lt;/strong&gt; — bug fixes, backwards compatible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This scheme is elegant precisely because it was designed for a specific problem: &lt;strong&gt;how do you communicate compatibility to a dependency resolver that can't read your changelog?&lt;/strong&gt; Tools like npm, Cargo, and Composer use SemVer ranges (&lt;code&gt;^2.4.0&lt;/code&gt;, &lt;code&gt;~1.2&lt;/code&gt;) to automatically resolve compatible versions. The version number &lt;em&gt;is&lt;/em&gt; machine-readable API documentation.&lt;/p&gt;

&lt;p&gt;SemVer becomes truly necessary when two conditions are met simultaneously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your software has a &lt;strong&gt;programmatic interface&lt;/strong&gt; (API, library, SDK, CLI used in scripts)&lt;/li&gt;
&lt;li&gt;You have &lt;strong&gt;external consumers you don't control&lt;/strong&gt; — third parties who can't update in lockstep with you&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open source libraries are the purest SemVer use case precisely because both conditions are maximally true: thousands of unknown downstream dependents, zero coordination possible. Your version number is the only signal you have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where SemVer fits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source libraries and packages (npm, Composer, Cargo, PyPI)&lt;/li&gt;
&lt;li&gt;Public REST APIs (the underlying package — not the &lt;code&gt;v1/v2&lt;/code&gt; surface, more on that below)&lt;/li&gt;
&lt;li&gt;SDKs and client libraries&lt;/li&gt;
&lt;li&gt;CLIs used in scripts and CI pipelines (where a renamed flag is a genuine breaking change)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Calendar Versioning: Honesty for Human Consumers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://calver.org" rel="noopener noreferrer"&gt;CalVer&lt;/a&gt; encodes the release date into the version number. Common formats include:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Used by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YY.MM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;25.04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YYYY.MINOR.PATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2025.1.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JetBrains IDEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YYYY.MM.DD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2025.04.22&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal build artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YYYYMMDD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;20250422&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI/CD pipeline artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YY.MM.PATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;25.04.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Twisted, pip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For user-facing software, CalVer is more honest than SemVer. The only thing that genuinely matters to a user is: &lt;em&gt;is this newer than what I have, and roughly when was it made?&lt;/em&gt; A date answers that directly. &lt;code&gt;2025.1&lt;/code&gt; tells you more than &lt;code&gt;4.7.2&lt;/code&gt; ever could, without pretending there's meaningful semantic signal in those numbers.&lt;/p&gt;

&lt;p&gt;Ubuntu's &lt;code&gt;YY.MM&lt;/code&gt; scheme is a great example: &lt;code&gt;24.10&lt;/code&gt; came out in October 2024, &lt;code&gt;25.04&lt;/code&gt; in April 2025. No ambiguity, no inflated MAJOR versions, no "what changed to warrant a 2.0?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where CalVer fits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop applications with regular release cadences&lt;/li&gt;
&lt;li&gt;Operating systems and distros&lt;/li&gt;
&lt;li&gt;Any user-facing software where "when was this released?" is more useful than "what changed semantically?"&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  API Versioning: A Special Case
&lt;/h2&gt;

&lt;p&gt;Public APIs have a unique challenge: they are programmatic interfaces (developer-facing), but the version isn't expressed as a package number managed by a resolver. It's expressed &lt;em&gt;in the interface itself&lt;/em&gt; — typically in the URL path, a header, or a media type.&lt;/p&gt;

&lt;p&gt;Common strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path versioning:&lt;/strong&gt; &lt;code&gt;/api/v1/users&lt;/code&gt;, &lt;code&gt;/api/v2/users&lt;/code&gt; — explicit, widely understood&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header versioning:&lt;/strong&gt; &lt;code&gt;API-Version: 2&lt;/code&gt; or &lt;code&gt;Accept: application/vnd.myapi.v2+json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query parameter:&lt;/strong&gt; &lt;code&gt;/api/users?version=2&lt;/code&gt; — simple but pollutes URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evergreen / no versioning:&lt;/strong&gt; the API evolves without breaking changes, consumers always get the latest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The version number here (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;) is just a sequential integer — what matters is the &lt;em&gt;strategy&lt;/em&gt; for where and how you express it. In practice, most teams pair this with SemVer internally: the public API is &lt;code&gt;v2&lt;/code&gt;, the underlying codebase is &lt;code&gt;2.4.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GraphQL&lt;/strong&gt; is interesting here because its design philosophy pushes toward the evergreen approach. Schema evolution should be additive — deprecate fields rather than removing them, add types rather than breaking existing ones. Explicit &lt;code&gt;v2&lt;/code&gt; GraphQL APIs are generally considered a design smell, a sign that the schema wasn't designed for evolution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Internal Interface Exception
&lt;/h2&gt;

&lt;p&gt;Having a programmatic interface doesn't automatically mean you need SemVer. A crucial condition is that &lt;strong&gt;you don't control the consumers&lt;/strong&gt; — and this has nothing to do with how your code is organized.&lt;/p&gt;

&lt;p&gt;Consider a Laravel application that serves both an Inertia.js frontend and a set of API routes. It's one codebase, one deployment, one &lt;code&gt;composer.json&lt;/code&gt;. If those API routes are only consumed by your own frontend or your own mobile app, you own both sides of the interface. You can coordinate breaking changes through a pull request or a Slack message. SemVer's signaling function adds no value because the version number isn't anyone's communication channel — your team is.&lt;/p&gt;

&lt;p&gt;The moment third-party developers start integrating against your &lt;code&gt;/api&lt;/code&gt; routes, that changes entirely. Now you have external consumers you can't coordinate with, and your API surface needs versioning discipline — expressed via route prefixing (&lt;code&gt;/api/v1/&lt;/code&gt;) or headers, regardless of how the underlying code is structured. The application version and the API version then become two separate concerns living in the same codebase, and only one of them needs SemVer.&lt;/p&gt;

&lt;p&gt;The key insight is that repository or project boundaries are irrelevant. What matters is the &lt;strong&gt;consumer boundary&lt;/strong&gt;: can you update all consumers when you make a breaking change, or not?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer type&lt;/th&gt;
&lt;th&gt;Scheme&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Human users&lt;/td&gt;
&lt;td&gt;CalVer, or marketing version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Programmatic, internal / same team&lt;/td&gt;
&lt;td&gt;Optional — coordination suffices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Programmatic, external / public&lt;/td&gt;
&lt;td&gt;SemVer or explicit API versioning required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What About Users Who Don't Update?
&lt;/h2&gt;

&lt;p&gt;Mobile apps and browser extensions raise an interesting edge case: users sometimes sit on old versions for weeks or months. Does that make the app's version number developer-facing?&lt;/p&gt;

&lt;p&gt;Not really — and the reason reveals something important about where versioning responsibility actually lives.&lt;/p&gt;

&lt;p&gt;When an old client is still in the wild, the compatibility burden falls on your &lt;strong&gt;backend API&lt;/strong&gt;, not on the app's version number. Your server has to keep supporting old clients — but that's an API versioning problem. The app version number itself still communicates nothing meaningful to the user in that scenario; they don't inspect &lt;code&gt;2.4.1&lt;/code&gt; and decide whether to update, they just tap a button in the App Store.&lt;/p&gt;

&lt;p&gt;The people who &lt;em&gt;do&lt;/em&gt; care about the old app version are your team internally — for analytics ("how many users are still on the old client?"), support triage, and deciding when to sunset old API compatibility. That's a valid operational concern, but it's solved by internal tooling, not by adopting SemVer for the app itself.&lt;/p&gt;

&lt;p&gt;The app version and the API version it consumes are two separate things that happen to ship together. Conflating them is a common source of unnecessary complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Platform-Specific Constraints
&lt;/h2&gt;

&lt;p&gt;Some platforms impose versioning formats regardless of your preferences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; requires a user-facing &lt;code&gt;CFBundleShortVersionString&lt;/code&gt; and an internal &lt;code&gt;CFBundleVersion&lt;/code&gt;. Both accept one to three period-separated integers — so &lt;code&gt;1.0&lt;/code&gt;, &lt;code&gt;25.04&lt;/code&gt;, and &lt;code&gt;2025.4.1&lt;/code&gt; are all valid. CalVer like &lt;code&gt;2025.04&lt;/code&gt; or SemVer like &lt;code&gt;1.4.2&lt;/code&gt; are therefore both technically supported. &lt;code&gt;CFBundleVersion&lt;/code&gt; must increase monotonically with every submission and is what the App Store actually tracks internally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; the same dual pattern — a human-readable &lt;code&gt;versionName&lt;/code&gt; which is a completely free-form string (any format works, including &lt;code&gt;2025.04&lt;/code&gt; or &lt;code&gt;1.4.2&lt;/code&gt;), and an integer &lt;code&gt;versionCode&lt;/code&gt; for the Play Store. Unlike iOS, &lt;code&gt;versionCode&lt;/code&gt; is strictly a single positive integer — no dots, no components — and has a maximum value of 2,100,000,000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome Extensions:&lt;/strong&gt; support 1 to 4 dot-separated integers (e.g. &lt;code&gt;1.0&lt;/code&gt;, &lt;code&gt;25.04&lt;/code&gt;, &lt;code&gt;1.2.3&lt;/code&gt;, or &lt;code&gt;2.0.0.1&lt;/code&gt;), each between 0 and 65535. The fourth component is optional — most extensions use three. Chrome uses this number to detect and trigger automatic updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all three cases, users almost never look at the version — the platform handles update prompts. The version number is mostly an artifact for the store and for your own release tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Software type&lt;/th&gt;
&lt;th&gt;Recommended scheme&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OSS library / package&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SemVer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dependency resolvers require it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public REST API surface&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;v1/v2 path or header&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explicit consumer contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL API&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Evergreen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Additive schema evolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDK / client library&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;SemVer&lt;/strong&gt;, major aligned to API&lt;/td&gt;
&lt;td&gt;Mirrors the API contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI (used in scripts/CI)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SemVer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flag changes are breaking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI (interactive only)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CalVer or SemVer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Either works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop app&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CalVer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Date is meaningful to users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile app&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SemVer-like + build number&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Platform-dictated hybrid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser extension&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1–4 part integer version&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome supports up to 4 components, 3 is typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS / distro&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CalVer + named releases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ubuntu, Fedora model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS / web app&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None public / date+SHA internally&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Continuous deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal services&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Date+build or sequential&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No external consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SemVer or sequential&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clarity over semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;The versioning scheme isn't a style choice — it's a communication tool. Before picking one, ask: &lt;em&gt;who needs to understand this version number, and what do they need to know?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If it's a &lt;strong&gt;dependency resolver&lt;/strong&gt; or an &lt;strong&gt;external developer&lt;/strong&gt; integrating against your interface, give them SemVer. They need to know whether your update is safe to adopt automatically.&lt;/p&gt;

&lt;p&gt;If it's a &lt;strong&gt;human user&lt;/strong&gt;, give them a date or a name. They need to know whether this is newer than what they have.&lt;/p&gt;

&lt;p&gt;And if it's an &lt;strong&gt;internal team&lt;/strong&gt; coordinating a deployment, a Git SHA or a build timestamp is probably enough — you have other channels for communicating what changed.&lt;/p&gt;

&lt;p&gt;The mistake isn't choosing the wrong scheme within a category. It's applying a scheme designed for one kind of consumer to an entirely different one.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>software</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>MongoDB vs MySQL: A Real-World Performance Showdown Using Disneyland Data</title>
      <dc:creator>Christian Holländer</dc:creator>
      <pubDate>Thu, 25 Sep 2025 11:40:40 +0000</pubDate>
      <link>https://forem.com/christian98/mongodb-vs-mysql-a-real-world-performance-showdown-using-disneyland-data-1i07</link>
      <guid>https://forem.com/christian98/mongodb-vs-mysql-a-real-world-performance-showdown-using-disneyland-data-1i07</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted over on my personal website. Maybe you want to check it out &lt;a href="https://christian-hollaender.de/posts/5-mongodb-vs-mysql-a-real-world-performance-showdown-using-disneyland-data" rel="noopener noreferrer"&gt;[HERE]&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;MongoDB vs MySQL head-to-head: We benchmarked both databases with real Disneyland data. MongoDB won writes by 67% and some queries by 98%, but MySQL had surprises too!&lt;/p&gt;




&lt;p&gt;When choosing a database for your next project, performance benchmarks often feel abstract and disconnected from real-world scenarios. That's why our recent university project comparing MongoDB and MySQL using actual Disneyland visitor data provides such valuable insights – we tested both databases against the same complex dataset with realistic use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dataset: Perfect for Database Testing
&lt;/h2&gt;

&lt;p&gt;We used a comprehensive Disneyland Paris dataset that presented real analytical challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wait times&lt;/strong&gt; for 37 attractions (2018-2019)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily attendance&lt;/strong&gt; data (2018-2022)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather conditions&lt;/strong&gt; (1999-2022)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Park operational schedules&lt;/strong&gt; (2018-2022)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This dataset was ideal for performance testing because it required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-frequency writes (weather data every hour, wait times every 15 minutes)&lt;/li&gt;
&lt;li&gt;Complex aggregations across multiple data sources&lt;/li&gt;
&lt;li&gt;Time-series analysis&lt;/li&gt;
&lt;li&gt;JOIN operations between related data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database Setup: Fair Fight Conditions
&lt;/h2&gt;

&lt;p&gt;To ensure a fair comparison, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identical hardware&lt;/strong&gt;: Same server (20GB RAM, 6 CPU cores, HDD storage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equivalent indexing&lt;/strong&gt;: Created the same indexes on both systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized schema&lt;/strong&gt;: Normalized data structure for MySQL, embedded documents for MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same queries&lt;/strong&gt;: Translated identical business logic between SQL and MongoDB aggregation pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Schema Differences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MySQL&lt;/strong&gt;: Traditional normalized approach with separate tables for weather descriptions, linked via foreign keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;: Document-based approach with embedded weather objects, eliminating the need for joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Results: The Numbers Don't Lie
&lt;/h2&gt;

&lt;p&gt;We ran each query 100 times sequentially to get reliable averages. Here are the results:&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Operations (Data Insertion)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;th&gt;MongoDB&lt;/th&gt;
&lt;th&gt;MongoDB Advantage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weather Data Insert&lt;/td&gt;
&lt;td&gt;56.7ms&lt;/td&gt;
&lt;td&gt;19.6ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;65.4% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wait Times Insert&lt;/td&gt;
&lt;td&gt;59.2ms&lt;/td&gt;
&lt;td&gt;19.3ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;67.4% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attendance Insert&lt;/td&gt;
&lt;td&gt;57.2ms&lt;/td&gt;
&lt;td&gt;19.3ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.2% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schedule Insert&lt;/td&gt;
&lt;td&gt;57.7ms&lt;/td&gt;
&lt;td&gt;28.4ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.7% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;: MongoDB consistently outperformed MySQL for write operations, often by more than 2:1 ratios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read Operations (Analytics Queries)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query Type&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;th&gt;MongoDB&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;RQ1&lt;/strong&gt;: Average wait times by attraction&lt;/td&gt;
&lt;td&gt;52.6 seconds&lt;/td&gt;
&lt;td&gt;1.0 seconds&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MongoDB (98.1% faster)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;RQ2&lt;/strong&gt;: Attendance by weather conditions&lt;/td&gt;
&lt;td&gt;413ms&lt;/td&gt;
&lt;td&gt;20.7 seconds&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MySQL (98% faster)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;RQ3&lt;/strong&gt;: Wait times vs weather correlation&lt;/td&gt;
&lt;td&gt;1h 12m 37s&lt;/td&gt;
&lt;td&gt;5ms*&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;RQ4&lt;/strong&gt;: Holiday vs regular attendance&lt;/td&gt;
&lt;td&gt;66.3ms&lt;/td&gt;
&lt;td&gt;58.5ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MongoDB (11.7% faster)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;*MongoDB query completed quickly, MySQL query failed after 4+ hours&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What These Results Actually Mean
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MongoDB's Strengths Revealed
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple Aggregations&lt;/strong&gt;: The 98% performance advantage in RQ1 shows MongoDB's aggregation pipeline excels at straightforward grouping and averaging operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complex Time-Based Queries&lt;/strong&gt;: RQ3's dramatic difference (MySQL took over an hour, MongoDB completed in milliseconds) demonstrates MongoDB's superiority for complex temporal correlations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write-Heavy Workloads&lt;/strong&gt;: Consistent 50-67% faster write performance makes MongoDB ideal for real-time data ingestion scenarios.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  MySQL's Surprising Victory
&lt;/h3&gt;

&lt;p&gt;RQ2 (weather-based attendance analysis) was 98% faster in MySQL. This query required complex temporary table operations and multiple data source correlations – an area where SQL's mature optimization still shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Why Behind the Performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MongoDB Advantages:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No JOIN overhead&lt;/strong&gt;: Embedded documents eliminate expensive table joins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized aggregation pipeline&lt;/strong&gt;: Purpose-built for analytical workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible indexing&lt;/strong&gt;: Better suited for document-based queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema flexibility&lt;/strong&gt;: No rigid structure constraints during writes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  MySQL Advantages:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mature query optimization&lt;/strong&gt;: Decades of SQL optimizer improvements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient temporary operations&lt;/strong&gt;: Better handling of complex intermediate results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory management&lt;/strong&gt;: Superior for certain multi-table operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Application Insights
&lt;/h2&gt;

&lt;p&gt;Based on our findings, choose:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB when you have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-frequency data ingestion (IoT sensors, real-time analytics)&lt;/li&gt;
&lt;li&gt;Time-series data analysis&lt;/li&gt;
&lt;li&gt;Simple to moderate aggregation needs&lt;/li&gt;
&lt;li&gt;Schema evolution requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MySQL when you have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex multi-source data correlations&lt;/li&gt;
&lt;li&gt;Heavy use of temporary tables and complex JOINs&lt;/li&gt;
&lt;li&gt;Mature application ecosystems expecting SQL&lt;/li&gt;
&lt;li&gt;Strict consistency requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Unexpected Learning: Context Matters
&lt;/h2&gt;

&lt;p&gt;The most valuable insight wasn't that one database is universally better – it's that &lt;strong&gt;workload characteristics determine the winner&lt;/strong&gt;. MongoDB dominated write operations and simple aggregations by massive margins, while MySQL excelled at complex relational operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Optimization Lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Index Strategy&lt;/strong&gt;: Both databases benefited equally from proper indexing on date fields and frequently queried attributes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query Design&lt;/strong&gt;: MongoDB's aggregation pipelines required different thinking than SQL, but often resulted in more maintainable code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hardware Considerations&lt;/strong&gt;: Even on modest hardware (HDD storage), the performance differences were dramatic, suggesting they'd be even more pronounced on modern SSD systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bottom Line for Database Selection
&lt;/h2&gt;

&lt;p&gt;Don't choose a database based on hype or familiarity alone. Our Disneyland data analysis proved that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-heavy applications&lt;/strong&gt;: MongoDB provides substantial advantages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex analytical queries&lt;/strong&gt;: Results vary dramatically by query type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed workloads&lt;/strong&gt;: Consider using both databases for different use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 98% performance differences we observed aren't edge cases – they represent real-world scenarios that could mean the difference between a responsive application and one that frustrates users.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This performance analysis was conducted as part of a Modern Database Systems course at TH Köln. The complete benchmark code and query implementations are available &lt;a href="https://gitlab.com/christian98-uni/mds" rel="noopener noreferrer"&gt;here&lt;/a&gt; for those interested in replicating these tests.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>mysql</category>
      <category>mongodb</category>
      <category>performance</category>
    </item>
    <item>
      <title>eBPF vs Sidecar Monitoring: What We Learned Building Both</title>
      <dc:creator>Christian Holländer</dc:creator>
      <pubDate>Sun, 13 Jul 2025 10:25:39 +0000</pubDate>
      <link>https://forem.com/christian98/ebpf-vs-sidecar-monitoring-what-we-learned-building-both-34m4</link>
      <guid>https://forem.com/christian98/ebpf-vs-sidecar-monitoring-what-we-learned-building-both-34m4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted over on my personal website. Maybe you want to check it out &lt;a href="https://christian-hollaender.de/posts/3-ebpf-vs-sidecar-monitoring-what-we-learned-building-both" rel="noopener noreferrer"&gt;[HERE]&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;We tried to replace sidecar monitoring with eBPF in Kubernetes. Spoiler: it didn't go as planned. Here's what we learned about hype vs reality in cloud-native monitoring, and when cutting-edge tech isn't always the answer.&lt;/p&gt;




&lt;p&gt;Last semester, my team and I embarked on an ambitious project: comparing eBPF-based monitoring with traditional sidecar proxy monitoring in Kubernetes clusters. What started as academic curiosity turned into a deep dive into kernel programming, developer experience, and the realities of cutting-edge technology adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise of eBPF
&lt;/h2&gt;

&lt;p&gt;Extended Berkeley Packet Filter (eBPF) has been making waves in the cloud-native world. The promise is compelling: instead of deploying a sidecar container in every pod to monitor network traffic, you can run a single eBPF program in the kernel that observes everything on the node. It sounds like magic—lower resource overhead, better security visibility, and simplified deployments.&lt;/p&gt;

&lt;p&gt;But is it really better? That's what we set out to discover.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Traditional Approach: Sidecar Proxies
&lt;/h2&gt;

&lt;p&gt;Before diving into eBPF, let's talk about how monitoring typically works today. The sidecar pattern has become the go-to approach for microservice monitoring. Tools like Envoy proxy sit alongside your application in the same pod, intercepting all network traffic and providing rich telemetry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good parts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Well-understood deployment patterns&lt;/li&gt;
&lt;li&gt;Rich ecosystem of tools and integrations&lt;/li&gt;
&lt;li&gt;Isolated from the host system&lt;/li&gt;
&lt;li&gt;Language and framework agnostic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The not-so-good parts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource overhead multiplied across every pod&lt;/li&gt;
&lt;li&gt;Additional complexity in pod configurations&lt;/li&gt;
&lt;li&gt;Potential single points of failure&lt;/li&gt;
&lt;li&gt;Network latency from additional hops&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Our eBPF Experiment
&lt;/h2&gt;

&lt;p&gt;We decided to build two functionally identical network monitors: one using the traditional sidecar approach and another using eBPF. Our goal was simple—track HTTP request timing and response sizes for services communicating in a Kubernetes cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reality Check
&lt;/h3&gt;

&lt;p&gt;Here's where things got interesting (read: frustrating). While the sidecar implementation was straightforward, the eBPF version became a lesson in humility.&lt;/p&gt;

&lt;p&gt;We chose Rust with the Aya framework, hoping to avoid the typical C development complexity associated with eBPF. The initial setup was smooth—Aya's templates got us started quickly. But implementing HTTP monitoring? That's where we hit the wall.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Challenges
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Memory Constraints&lt;/strong&gt;: eBPF programs are limited to a 512-byte stack. Try parsing HTTP payloads with that constraint. Every function call needs to be carefully designed to avoid stack overflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Verifier&lt;/strong&gt;: eBPF's safety verifier is both a blessing and a curse. It prevents crashes and security issues, but it's incredibly strict about memory access patterns. We spent countless hours fighting "invalid memory access" errors even with proper bounds checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limited Debugging&lt;/strong&gt;: Forget about your favorite debugger. eBPF debugging involves a lot of &lt;code&gt;bpf_printk()&lt;/code&gt; statements and careful reading of verifier logs. The development experience feels like programming in the 1990s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ecosystem Maturity&lt;/strong&gt;: While there are excellent eBPF tools like Pixie and Cilium, rolling your own solution requires deep kernel knowledge. The learning curve is steep, and the documentation assumes significant background knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Discovered
&lt;/h2&gt;

&lt;p&gt;After weeks of development, we had to face facts: we couldn't complete our eBPF HTTP monitor in the timeframe. But this "failure" taught us valuable lessons.&lt;/p&gt;

&lt;h3&gt;
  
  
  eBPF Isn't Magic
&lt;/h3&gt;

&lt;p&gt;The hype around eBPF is real, but it's not a silver bullet. It excels at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-level network analysis&lt;/li&gt;
&lt;li&gt;System-wide visibility&lt;/li&gt;
&lt;li&gt;Performance monitoring&lt;/li&gt;
&lt;li&gt;Security enforcement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for application-layer monitoring like HTTP request tracing? The traditional approaches are often more practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Developer Experience Gap
&lt;/h3&gt;

&lt;p&gt;Moving from sidecar configuration to eBPF programming shifts the burden from operations teams to developers. This isn't necessarily bad, but it requires different skills. You're essentially doing kernel programming, which most application developers haven't done since their systems programming course.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Choose What
&lt;/h3&gt;

&lt;p&gt;Based on our experience, here's our take:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose eBPF when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need system-wide visibility&lt;/li&gt;
&lt;li&gt;Performance overhead is critical&lt;/li&gt;
&lt;li&gt;You're building infrastructure tools&lt;/li&gt;
&lt;li&gt;Your team has kernel programming expertise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stick with sidecars when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need application-layer insights&lt;/li&gt;
&lt;li&gt;Your team values quick iteration&lt;/li&gt;
&lt;li&gt;You want mature tooling and support&lt;/li&gt;
&lt;li&gt;Operational simplicity matters&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Our experiment confirmed something important: technology choices aren't just about technical capabilities—they're about team capabilities, operational complexity, and long-term maintainability.&lt;/p&gt;

&lt;p&gt;eBPF is incredibly powerful, but with great power comes great complexity. For most teams monitoring microservices, mature solutions like Envoy proxy or ready-made eBPF tools like Pixie offer the best balance of capability and usability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prototype early&lt;/strong&gt;: We should have built a minimal eBPF program first to understand the constraints before committing to a complex implementation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leverage existing tools&lt;/strong&gt;: Unless you have specific requirements, using battle-tested solutions like Cilium or Pixie is probably smarter than rolling your own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider the total cost&lt;/strong&gt;: The "cost" of a technology includes development time, debugging complexity, and ongoing maintenance—not just runtime overhead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Know your use case&lt;/strong&gt;: eBPF shines for infrastructure and low-level monitoring, but application-layer observability might be better served by traditional approaches.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;While our eBPF monitor didn't cross the finish line, the experience was invaluable. We gained deep insights into kernel programming, network monitoring, and the practical challenges of adopting cutting-edge technology.&lt;/p&gt;

&lt;p&gt;eBPF is undoubtedly the future of many aspects of systems programming. But like any powerful technology, it requires careful consideration of when and how to apply it. Sometimes the boring, well-understood solution is exactly what you need.&lt;/p&gt;

&lt;p&gt;For teams considering eBPF for monitoring, my advice is simple: start with existing tools, understand your specific requirements, and be prepared for a steep learning curve if you decide to build custom solutions.&lt;/p&gt;

&lt;p&gt;The future of cloud-native monitoring is exciting, but it's built on a foundation of understanding both the possibilities and the trade-offs of the tools at our disposal.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is based on research conducted as part of a university project exploring eBPF applications in Kubernetes environments. While our implementation didn't reach completion, the insights gained about technology adoption and developer experience proved invaluable.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to create a shared Prettier configuration</title>
      <dc:creator>Christian Holländer</dc:creator>
      <pubDate>Fri, 14 Oct 2022 13:13:25 +0000</pubDate>
      <link>https://forem.com/christian98/how-to-create-a-shared-prettier-configuration-3284</link>
      <guid>https://forem.com/christian98/how-to-create-a-shared-prettier-configuration-3284</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted over on my personal website. Maybe you want to check it out &lt;a href="https://christian-hollaender.de/posts/2-how-to-create-a-shared-prettier-configuration"&gt;[HERE]&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Keeping configs for styling and linting tools consistent across multiple projects can be challenging. Many tools support shared configs, which are created once and used everywhere. This article will discover how to create a shared configuration for prettier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently we decided, that we want to have a common styling on how to write our Typescript code. This would make it easier for us to read and understand code others had written or even that we have written ourselves a few months ago. Therefore we agreed on integrating &lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;. An opinionated code formatter.&lt;/p&gt;

&lt;p&gt;When we first adopted prettier on all our dozens of projects we copy/pasted the configuration from one project to the other. As we updated the config to fit our needs we were faced with inconsistent options in our projects. So we needed to figure out, how to change the prettier options globally for all our projects at the same time. Luckily the prettier maintainers got you covered. They already thought of that problem and provide a native way to share the config across multiple projects by creating a separate npm package. (&lt;a href="https://prettier.io/docs/en/configuration.html#sharing-configurations"&gt;Sharing Configurations&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In this guide, I want to show you how we structured our shared config to meet several projects with different prettier plugins, like the phenomenal &lt;a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss"&gt;Tailwind Prettier plugin&lt;/a&gt;. But also to make it extensible if a project needs some special config options only for this specific project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;First of all, we need to initialize our repository. As I love typescript I wanted that the shared config itself is written in typescript and gets transpiled to javascript through a built-step. I will do this by using Rollup.js, but Webpack should do the trick as well. So let’s get started..&lt;/p&gt;

&lt;p&gt;Installing Typescript should be straightforward by running &lt;code&gt;npm i -D typescript&lt;/code&gt; and creating a &lt;code&gt;tsconfig.json&lt;/code&gt; in the project root. I am using this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"importHelpers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"experimentalDecorators"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"emitDecoratorMetadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowSyntheticDefaultImports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declarationDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declarationMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"typeRoots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/@types"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"es2015"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"es2017"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I want to initialize the Rollup.js setup by adding the &lt;code&gt;rollup.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;excludeDependenciesFromBundle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-exclude-dependencies-from-bundle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollup/plugin-node-resolve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;commonjs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rollup/plugin-commonjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;typescript&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-typescript2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;visualizer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-visualizer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// eslint-disable-next-line @typescript-eslint/no-var-requires&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packageJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./package.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inlineDynamicImports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;packageJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;excludeDependenciesFromBundle&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;commonjs&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;typescript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;useTsconfigDeclarationDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nx"&gt;visualizer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;package-deps.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sunburst&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;gzipSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;brotliSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I will skip the installation of the necessary dependencies, as I think you already know how to do this when you are interested in creating a shared prettier config ;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we are finally ready to actually create the shared config in our &lt;code&gt;src&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prettier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;singleQuote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bracketSameLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tabWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.{json,yml,yaml}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;tabWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.{yml,yaml}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;singleQuote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;bracketSpacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least I want to show you a cool little trick to use the just-built config right in this project. When do this by creating a &lt;code&gt;prettier.config.js&lt;/code&gt; file in our project root. In there we put the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('prettier').Options} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/prettier-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are actually just importing our build module and re-export it so our prettier command can find it. But note: You need to build before you can apply the changes of the shared config to the project because there is a built-step involved!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automatic Package Releases using Semantic-Release</title>
      <dc:creator>Christian Holländer</dc:creator>
      <pubDate>Sat, 24 Sep 2022 21:08:23 +0000</pubDate>
      <link>https://forem.com/christian98/automatic-package-releases-using-semantic-release-8k1</link>
      <guid>https://forem.com/christian98/automatic-package-releases-using-semantic-release-8k1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted over on my personal website. Maybe you want to check it out &lt;a href="https://christian-hollaender.de/posts/1-automatic-package-releases-using-semantic-release"&gt;[HERE]&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Writing the release notes and Changelog manually is a time-consuming and complicated task. And then you need to push your new release to your desired Package-registry like NPM or Packagist. Fortunately, there is a better way to do this. Join me in discovering an alternative.&lt;/p&gt;




&lt;p&gt;When you already maintained an open-source package or developed a package at work, you might know how complicated the job of releasing a new version can be. Most of the time the process contains the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scim all Commits, Issues, and PRs to see what changed&lt;/li&gt;
&lt;li&gt;Create a list of the previously collected changes for your Changelog and Release-Notes&lt;/li&gt;
&lt;li&gt;Decide if you want to create a patch, minor or major release (and probably have to ask the author of the changes)&lt;/li&gt;
&lt;li&gt;If your package provides the version to its users, update it&lt;/li&gt;
&lt;li&gt;Add a new Git tag to the repository and let your CI/CD build one last time&lt;/li&gt;
&lt;li&gt;Push the newly built package to the Registry of your liking (don't forget to authenticate 😉)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fortunately, there is an easy way to automate all of this. It's called CI/CD. Personally, I use Gitlab CI/CD as all my Code is hosted on Gitlab. But Github Actions and other providers should work similarly. So now we need to think about how we can achieve all our tasks in an automated manner. We as developers are lazy people, so writing a bunch of bash scripts all ourselves is not really an option. Luckily there is a tool we can use to help out:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://github.com/semantic-release/semantic-release"&gt;Semantic-Release&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Semantic-Release is a tool that automates most of the hard tasks needed to release a new version of our package. It actually can also release new versions of our application, but that is a bit more difficult. Its plugin system makes it easy and extendable for multiple package managers and steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  So let's get started:
&lt;/h3&gt;

&lt;p&gt;For this example we want to publish a new NPM package as this is the easiest, to begin with.&lt;/p&gt;

&lt;p&gt;First, install the necessary packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/npm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I installed the main package &lt;code&gt;semantic-release&lt;/code&gt; with some plugins that I need for my setup. There is a good list of official and community plugins on the documentation page of the project: &lt;a href="https://semantic-release.gitbook.io/semantic-release/extending/plugins-list"&gt;https://semantic-release.gitbook.io/semantic-release/extending/plugins-list&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also add a script section to your &lt;code&gt;package.json&lt;/code&gt;. For me, it was actually necessary. Otherwise, I could not run the command inside my CI/CD environment. Because it was not in my &lt;code&gt;PATH&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"semantic-release"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"semantic-release"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we can run Semantic Release, we need to configure the project so that releasing works. This is especially important if you don't want to push a package into the normal NPM registry, but want to use the GitLab registry as I do.&lt;br&gt;
For this, we have to adjust the "publishConfig" in the &lt;code&gt;package.json&lt;/code&gt;. Enter there the URL of your registry. For me, this is the project-specific URL of the GitLab registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@&amp;lt;package-scope&amp;gt;:registry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gitlab.com/api/v4/projects/&amp;lt;gitlab-project-id&amp;gt;/packages/npm/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Do not set the &lt;code&gt;private&lt;/code&gt; attribute in your &lt;code&gt;package.json&lt;/code&gt; to true, even though it is an internal/private package. This will prevent Semantic-Release from publishing your package to ANY registry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After that, we still need to authenticate against our internal registry. For this, we use the so-called &lt;code&gt;.npmrc&lt;/code&gt; file. In this file, we configure our package scope with a target URL and a personal access token for authentication. I found it easiest to put the scope definition directly into the repo. The token setting must never happen in the repo, otherwise, you expose the token to possibly unauthorized third parties.&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;package-scope&amp;gt;:registry=https://gitlab.com/api/v4/projects/&amp;lt;gitlab-project-id&amp;gt;/packages/npm/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can set your personal access token for local development in your &lt;code&gt;~/.npmrc&lt;/code&gt; file. This way you can also authenticate in other projects on your machine and you do not accidentally commit your token to the repo. You can do it 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;//gitlab.com/api/v4/packages/npm/:_authToken=&amp;lt;access-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Yes the syntax with the &lt;code&gt;//&lt;/code&gt; at the beginning is correct. It is rather strange, but this is how it is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As we also need the token in our CI/CD we need to set this inside our pipeline job. I did this like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"&amp;gt;.npmrc&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "${CI_API_V4_URL#https?}/packages/npm/:_authToken=${CI_JOB_TOKEN}"&amp;gt;&amp;gt;.npmrc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I completely overwrite the &lt;code&gt;.npmrc&lt;/code&gt; with variables provided by the CI environment. The nice thing about GitLab is, that it already provides all necessary tokens and URLs. I'll show the complete Job-configuration later on.&lt;/p&gt;

&lt;p&gt;Now that we configured everything for NPM to publish a package, we also need to configure Semantic-Release so that we can trigger the releases correctly. Therefore we can use a variety of file formats. The most popular ones are JSON and Javascript. You can find out more &lt;a href="https://semantic-release.gitbook.io/semantic-release/usage/configuration#configuration-file"&gt;here&lt;/a&gt;. For this demo I chose &lt;code&gt;release.config.js&lt;/code&gt; cause it gives us the most dynamic way to configure Semantic-Release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import("semantic-release").Options } */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@semantic-release/commit-analyzer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@semantic-release/release-notes-generator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@semantic-release/gitlab&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@semantic-release/npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;branches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+([0-9])?(.{+([0-9]),x}).x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-major&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;prerelease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alpha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;prerelease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Configuring the branches is currently only necessary because we already use the new git naming for our default branch "main". If you are still using "master", you won't need to configure them.&lt;/p&gt;

&lt;p&gt;If you are using TypeScript like me, you can also install &lt;code&gt;@types/semantic-release&lt;/code&gt; and use the shown type doc, to get autocompletion in most IDEs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Add CI job
&lt;/h2&gt;

&lt;p&gt;Last but not least we need to configure our already mentioned CI-Job. This depends on your environment. As I am using Gitlab-CI/CD I can only show how this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:18.7&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"&amp;gt;.npmrc&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "${CI_API_V4_URL#https?}/packages/npm/:_authToken=${CI_JOB_TOKEN}"&amp;gt;&amp;gt;.npmrc&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run semantic-release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing you will also need to set is an environment Token called: &lt;code&gt;GITLAB_TOKEN&lt;/code&gt;. This is needed for the Gitlab-plugin and to be able to upload Releases to Gitlab. You can find these in the left menu under &lt;code&gt;Deployments &amp;gt; Releases&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;To use this automatic release setup, you need to use a special Commit syntax. It is actually quite simple, but you need to memorize all the different keys. Like if you write a commit message like &lt;code&gt;feat: some dope new feature&lt;/code&gt;, Semantic-Release will trigger a minor update of your package. If you use &lt;code&gt;fix: some small bug fix&lt;/code&gt;, it will trigger a patch release. You can also always do a major release by adding &lt;code&gt;BREAKING CHANGE: Description what changes.&lt;/code&gt; to your commit. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat: we added a non backward compatible change

BREAKING CHANGE: Please now use this and that.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This should be it. Now you are all set to automatically release your package with the special Commit-Syntax.&lt;br&gt;
If you want to know more I highly suggest reading through the amazing &lt;a href="https://semantic-release.gitbook.io/semantic-release/"&gt;documentation&lt;/a&gt;. I have already mentioned it here and there throughout this post.&lt;/p&gt;

&lt;p&gt;And now the only thing left is to say thank you for reading!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
