<?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: Rex</title>
    <description>The latest articles on Forem by Rex (@myungsunrex).</description>
    <link>https://forem.com/myungsunrex</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%2F3019197%2F1696a21b-98a9-4e5c-b2bf-4e36519de884.png</url>
      <title>Forem: Rex</title>
      <link>https://forem.com/myungsunrex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/myungsunrex"/>
    <language>en</language>
    <item>
      <title>TypeWire 0.1.0 — Explicit, Transparent DI for TypeScript</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Mon, 21 Apr 2025 18:13:37 +0000</pubDate>
      <link>https://forem.com/myungsunrex/typewire-010-explicit-transparent-di-for-typescript-j8k</link>
      <guid>https://forem.com/myungsunrex/typewire-010-explicit-transparent-di-for-typescript-j8k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; TypeWire is a small DI library for TypeScript — no decorators, no magic, just typed wires and explicit registration. Designed for clarity and composability as your system scales.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In recent posts, I’ve been writing about architectural clarity — how good boundaries come from assigning clear ownership and avoiding ambient behaviors. That doesn’t just apply to services or classes — it also applies to how we wire them together.&lt;br&gt;
&lt;strong&gt;Constructors and factories are part of your system’s behavior.&lt;/strong&gt; They may not drive core business logic directly, but they shape how that logic can be composed and reused.&lt;/p&gt;

&lt;p&gt;When we manually stitch them together across files and modules, boundaries blur. &lt;br&gt;
It’s not always obvious at first — but as the system grows, that blur often becomes expensive over time.&lt;/p&gt;

&lt;p&gt;We already have solutions to this problem — DI frameworks and libraries. But many come with known challenges and friction in practice.&lt;/p&gt;

&lt;p&gt;Given these known problems, &lt;a href="https://github.com/typewirets/typewirets" rel="noopener noreferrer"&gt;&lt;strong&gt;TypeWire&lt;/strong&gt;&lt;/a&gt;, is my attempt to experiment on&lt;br&gt;
solution to the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeWire&lt;/strong&gt; is a small, transparent DI library for TypeScript — designed to formalize those boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 TypeWire 0.1.0 is now live
&lt;/h2&gt;

&lt;p&gt;📦 &lt;a href="https://www.npmjs.com/package/@typewirets/core" rel="noopener noreferrer"&gt;&lt;code&gt;@typewirets/core&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
📦 &lt;a href="https://www.npmjs.com/package/@typewirets/inversify" rel="noopener noreferrer"&gt;&lt;code&gt;@typewirets/inversify&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
📦 &lt;a href="https://www.npmjs.com/package/@typewirets/react" rel="noopener noreferrer"&gt;&lt;code&gt;@typewirets/react&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
📝 &lt;a href="https://github.com/typewirets/typewirets/releases/tag/v0.1.0" rel="noopener noreferrer"&gt;Release Notes&lt;/a&gt;&lt;br&gt;
📘 &lt;a href="https://github.com/typewirets/typewirets" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Another DI library
&lt;/h2&gt;

&lt;p&gt;Frankly, just for fun, but also to validate my hypothesis while having fun.&lt;/p&gt;

&lt;p&gt;Most TypeScript DI systems borrow heavily from large enterprise-style frameworks. This often results in heavy decorator usage, with steep learning curve,&lt;br&gt;
and difficulties in tracing actual implementations. These DI frameworks also creates very tight coupling to the framework itself.&lt;/p&gt;

&lt;p&gt;None of them are usually the problem, if you're fully committed to the framework. For those who are looking for lightweight solution without wanting to tie oneself to a framework, or for those who are working with libraries that should not be tied into specific framework can be very challenging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeWire&lt;/strong&gt; attempts to address above issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declarative &lt;strong&gt;TypeWire&lt;/strong&gt; Definition:

&lt;ul&gt;
&lt;li&gt;Required dependencies - or could be delegated further.&lt;/li&gt;
&lt;li&gt;A unique symbol representing the wire.&lt;/li&gt;
&lt;li&gt;A factory or constructor on how to instantiate an instance of any type.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Each wire exposes two key methods - &lt;code&gt;getInstance&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; - that abstract container interactions

&lt;ul&gt;
&lt;li&gt;Allows these TypeWire declarations to be used in compatible DI framework of choice (limited though)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;These declarations shift ownership of construction and dependency resolution to each wire — rather than centralizing it in the container or framework.&lt;/p&gt;

&lt;p&gt;Combined with &lt;strong&gt;explicit symbol registration&lt;/strong&gt;, there’s no guessing, no tagging, and no inference via decorators. Just types and functions.&lt;/p&gt;

&lt;p&gt;This makes DI feel like a natural modeling tool — not an abstract mechanism.&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit, self-contained registration&lt;/li&gt;
&lt;li&gt;Clear boundaries between resolution, behavior, and configuration&lt;/li&gt;
&lt;li&gt;Decoupling from framework-specific conventions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠 Example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usual code example
&lt;/h3&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;typeWireOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypeWireContainer&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;@typewirets/core&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&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;userServiceWire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;typeWireOf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createWith&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;staticUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="c1"&gt;// or use class&lt;/span&gt;
    &lt;span class="c1"&gt;// the factory function clearly states what we are creating&lt;/span&gt;
    &lt;span class="c1"&gt;// We don't have to make this inline.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;staticUsers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;UserService&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userControllerWire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;typeWireOf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserController&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userServiceWire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;createWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&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;container&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;TypeWireContainer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// loads all dependant wires as well&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userControllerWire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// more settings&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;///&lt;/span&gt;

  &lt;span class="c1"&gt;// Core business logic section&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&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;userController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userControllerWire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... etc&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Override someone else's wire
&lt;/h3&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;typeWireOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypeWireContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;typeWireGroupOf&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;@typewirets/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// from the example above&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main2&lt;/span&gt;&lt;span class="p"&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;container&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;TypeWireContainer&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;wireGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;typeWireGroupOf&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;userControllerWire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// overrides while keeping the original type token&lt;/span&gt;
    &lt;span class="nx"&gt;userServiceWire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withCreator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// returned value must match type of the original wire.&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UserService is not in a good vibe&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="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;// loads all dependant wires as well await wireGroup.apply(container);&lt;/span&gt;

  &lt;span class="c1"&gt;// more settings&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;///&lt;/span&gt;

  &lt;span class="c1"&gt;// Core business logic section&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&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;userController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userControllerWire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... etc&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Controller&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should use mock service&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="o"&gt;=&amp;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;container&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;TypeWireContainer&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;wireGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;typeWireGroupOf&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;userControllerWire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;userServiceWire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withCreator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// explicit mocking!!!&lt;/span&gt;
          &lt;span class="c1"&gt;// no more dancing with jest.mock or vi.mock with complicated hoisting.&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knownValue&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;someExpectedUser&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;

              &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not in a good test vibe&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="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
     &lt;span class="p"&gt;]);&lt;/span&gt;


      &lt;span class="c1"&gt;// loads all dependant wires as well&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wireGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// do the test&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;p&gt;You can use classes, factory functions, or async constructors — TypeWire doesn’t impose a pattern. It just helps formalize the glue once your system has enough moving parts that manual wiring becomes a bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 Related Articles
&lt;/h2&gt;

&lt;p&gt;TypeWire builds on themes from my recent architecture series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clarity in Architecture: Not OOP vs FP &lt;a href="https://dev.to/myungsunrex/clarity-in-architecture-not-oop-vs-fp-2mhf"&gt;https://dev.to/myungsunrex/clarity-in-architecture-not-oop-vs-fp-2mhf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Constructing Boundaries (Not Just Using Classes) &lt;a href="https://dev.to/myungsunrex/part-2-constructing-boundaries-not-just-using-classes-5gh9"&gt;https://dev.to/myungsunrex/part-2-constructing-boundaries-not-just-using-classes-5gh9&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Internal State: Not Evil, Just Misplaced &lt;a href="https://dev.to/myungsunrex/internal-state-not-evil-just-misplaced-20dh"&gt;https://dev.to/myungsunrex/internal-state-not-evil-just-misplaced-20dh&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dependency Injection Without Magic &lt;a href="https://dev.to/myungsunrex/dependency-injection-without-magic-39k4"&gt;https://dev.to/myungsunrex/dependency-injection-without-magic-39k4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Functional Principles Still Matter &lt;a href="https://dev.to/myungsunrex/functional-principles-still-matter-2onh"&gt;https://dev.to/myungsunrex/functional-principles-still-matter-2onh&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever struggled with messy construction logic, blurry service boundaries, or DI that makes your app harder to reason about — this might help.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Still Early — But Stable
&lt;/h2&gt;

&lt;p&gt;This is version 0.1.0. The container system and resolution APIs are stable, and real-world ready (I think 😉). &lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Feedback Welcome
&lt;/h2&gt;

&lt;p&gt;If you’re building something in TypeScript — especially in a monorepo or layered architecture — and want to keep things transparent as they scale, try it out.&lt;/p&gt;

&lt;p&gt;Would love to hear thoughts, issues, or improvements from others thinking about these problems too.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://github.com/typewirets/typewirets/" rel="noopener noreferrer"&gt;http://github.com/typewirets/typewirets/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>dependencyinjection</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Functional Principles Still Matter</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Thu, 17 Apr 2025 22:06:43 +0000</pubDate>
      <link>https://forem.com/myungsunrex/functional-principles-still-matter-2onh</link>
      <guid>https://forem.com/myungsunrex/functional-principles-still-matter-2onh</guid>
      <description>&lt;p&gt;There’s a wide range of opinions when it comes to functional principles. Out of the many points people debate, I tend to value the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declarativeness&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Immutability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composition&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, humans perceive the real world through side effects and imperative steps. Describing real-world problems purely through functional abstractions can be challenging — or at least requires a significant shift in perspective. &lt;/p&gt;

&lt;p&gt;Building a world full of functional declarativeness often demands multiple layers of indirection, which may not always be intuitive or practical.&lt;/p&gt;

&lt;p&gt;Consider some common realities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration pulled from the environment
&lt;/li&gt;
&lt;li&gt;APIs that fail
&lt;/li&gt;
&lt;li&gt;User input that mutates internal state
&lt;/li&gt;
&lt;li&gt;Business logic that depends on time, randomness, or context
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite these constraints, certain problems &lt;em&gt;are&lt;/em&gt; better described — and understood — when a few functional principles are applied.&lt;/p&gt;

&lt;p&gt;So rather than force the world to fit a functional model, I’ve landed on a middle ground.&lt;/p&gt;

&lt;p&gt;I primarily strive for &lt;strong&gt;predictability&lt;/strong&gt; and &lt;strong&gt;composability&lt;/strong&gt; in my systems, and I’ll use whatever principles — functional or otherwise — that help me get there.&lt;/p&gt;

&lt;p&gt;And functional principles &lt;em&gt;do&lt;/em&gt; help me get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purity and Composition
&lt;/h2&gt;

&lt;p&gt;As we briefly touched on earlier, it's nearly impossible — and not particularly practical — to achieve 100% purity in most functions. But we can still define boundaries such that the &lt;strong&gt;behaviors&lt;/strong&gt; feel pure, even if the environment around them is not.&lt;/p&gt;

&lt;p&gt;Let’s start with a simple validator:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchemaService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// schemaService invokes side effects, so the function is not pure&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function isn’t pure — it depends on schemaService, which might fetch or mutate data behind the scenes. But just like in mathematics, we can often reason about complex systems by introducing constraints.&lt;/p&gt;

&lt;p&gt;If we constrain the behavior of schemaService — say, through interface boundaries, mocks, or scoped inputs — then validate() becomes more predictable. That’s often good enough.&lt;/p&gt;

&lt;p&gt;In testing, we already do this implicitly: we mock schemaService to make the function behave in a controlled way.&lt;/p&gt;

&lt;p&gt;But now consider composability:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Use in Another Function&lt;/em&gt;&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaAndDoMore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// where should it come from?&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schemaId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&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="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using validate() elsewhere, we need to provide schemaService. In practice, that leaves us with a few choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass dependencies down through injection chains&lt;/li&gt;
&lt;li&gt;Import them directly as singletons (tight coupling)&lt;/li&gt;
&lt;li&gt;Pass a container or context to retrieve dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Extend the Function to Include Notification&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s two ways to compose validate() with a notification system:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Option 1: Fully Injected&lt;/em&gt;&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateAndNotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchemaService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;p&gt;&lt;em&gt;Option 2: Fully Imported&lt;/em&gt;&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="nx"&gt;schemaService&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;./schema-service&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;notificationService&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;./notification-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateAndNotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;p&gt;Each has trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One pushes dependencies through the call chain (verbose, but explicit)&lt;/li&gt;
&lt;li&gt;The other hides them behind module imports (convenient, but tightly coupled)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where objects or classes come into play — not because we want to model the world with objects, but because they help structure the environment around behaviors.&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;KnownSchemaValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worldAroundMe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&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;schemaId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;knownSchemaId&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;schemaService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worldAroundMe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schemaService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// validation logic here&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CoreBusinessValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worldAroundMe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&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;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KnownSchemaValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worldAroundMe&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;notificationService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worldAroundMe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// core business validation + notification&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;p&gt;By injecting “the world around me,” we isolate external dependencies while preserving &lt;strong&gt;pure-ish behavior contracts&lt;/strong&gt;.&lt;br&gt;
In other words: we separate &lt;strong&gt;what we control&lt;/strong&gt; (function parameters) from &lt;strong&gt;what we manage&lt;/strong&gt; (injected services), creating the illusion of purity within well-defined constraints.&lt;br&gt;
Of course, if a function can be truly pure — that’s ideal. But wrapping it in a factory or an object to inject external context isn’t about over-engineering for every possible future.&lt;br&gt;
It’s a small, intentional step that makes extension easier later.&lt;br&gt;
And if my assumptions turn out wrong, I can revisit the boundary and rethink the contract — no sunk cost, just an adaptation.&lt;br&gt;
As long as we maintain good boundaries and clear contracts, we can reason about and test these functions &lt;em&gt;as if&lt;/em&gt; they were pure — even when they’re not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve found far more success in &lt;strong&gt;structuring for purity&lt;/strong&gt; than in chasing purity for its own sake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This mindset applies across the board — whether I’m shaping business rules, transforming data, or designing system behaviors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Immutability
&lt;/h2&gt;

&lt;p&gt;Immutability is a great tool to use to improve &lt;strong&gt;predictability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a value is declared as immutable and never changes, great — that’s easy to reason about.&lt;br&gt;
But if a value is mutable, and that’s clearly declared and understood — that’s fine too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The real problem isn’t mutation — it’s &lt;em&gt;unexpected&lt;/em&gt; mutation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Some languages help you with this through the type system (like TypeScript, Rust, or Elm).&lt;br&gt;
Others, like JavaScript or Python, leave it to convention and discipline.&lt;/p&gt;

&lt;p&gt;Either way, the goal is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Declare your intent.&lt;br&gt;
Stick to that intent.&lt;br&gt;
The intent itself is a contract.&lt;br&gt;
That’s usually good enough.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build Behavior from Small Pieces
&lt;/h2&gt;

&lt;p&gt;Functional thinking isn’t just about functions — it’s about how we &lt;strong&gt;structure systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’ve found the most success in systems that are &lt;strong&gt;composed from focused units&lt;/strong&gt; — each owning a clear behavior or decision. This works better than trying to centralize everything into one orchestrator or “God class.”&lt;/p&gt;

&lt;p&gt;Some patterns I lean on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep orchestration shallow — let domain logic live in reusable units&lt;/li&gt;
&lt;li&gt;Group behavior by &lt;strong&gt;cohesion&lt;/strong&gt;, not inheritance&lt;/li&gt;
&lt;li&gt;Compose systems by layering contracts, not hard-coded call chains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s about making change easier — and more localized — when the system grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functional Principles Aren’t the Opposite of OOP
&lt;/h2&gt;

&lt;p&gt;Functional and object-oriented principles can (and often should) coexist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use classes to own lifecycle and boundaries&lt;/li&gt;
&lt;li&gt;Use functions to express clear, testable logic&lt;/li&gt;
&lt;li&gt;Use DI to wire them together without magic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, all of this is in service of one thing: clarity.&lt;/p&gt;

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

&lt;p&gt;Functional programming offers some great tools — but the value comes from how we apply them, not whether we follow the paradigm perfectly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purity gives us control over behavior.&lt;/li&gt;
&lt;li&gt;Immutability gives us clarity over data.&lt;/li&gt;
&lt;li&gt;Composition gives us structure and scalability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code can be structured in a way that achieves all of the above — even if, underneath, it’s not written in a purely functional style.&lt;/p&gt;

&lt;p&gt;With clear boundaries and well-defined contracts, systems become easier to understand, test, and extend.&lt;/p&gt;

&lt;p&gt;And whether I’m working in a class-heavy codebase or a function-first one, those principles still apply.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>softwaredesign</category>
    </item>
    <item>
      <title>🧩 Dependency Injection Without Magic</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Wed, 16 Apr 2025 19:26:19 +0000</pubDate>
      <link>https://forem.com/myungsunrex/dependency-injection-without-magic-39k4</link>
      <guid>https://forem.com/myungsunrex/dependency-injection-without-magic-39k4</guid>
      <description>&lt;p&gt;Dependency Injection has a reputation problem. Between bloated frameworks and decorator-heavy configs, it often feels like “too much magic.”&lt;/p&gt;

&lt;p&gt;But at its core, DI is simple: It’s simply the act of injecting dependencies via a factory or constructor. It can be done manually, or with some library or framework support. At the end of the day, it’s an essential technique for creating clear boundaries between components.&lt;/p&gt;

&lt;p&gt;For example, following code is using DI:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserAPIClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getUserById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotFound&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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="c1"&gt;// The second example also uses DI via function parameters — but it doesn’t encapsulate behavior, making it slightly harder to extend.&lt;/span&gt;
&lt;span class="c1"&gt;// slightly more diffcult to extend&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserAPIClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotFound&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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;p&gt;While DI is a simple technique for drawing clear boundaries, I’ve found it helpful to separate its use for shared singleton behavior vs context-specific behavior.&lt;/p&gt;

&lt;p&gt;Used thoughtfully, DI helps you write code that’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to test&lt;/li&gt;
&lt;li&gt;Easy to understand&lt;/li&gt;
&lt;li&gt;Easy to refactor&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Role of DI: Composing Services, Not Context
&lt;/h3&gt;

&lt;p&gt;Dependency Injection shines when handling long-lived, shared, and reusable logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;API clients&lt;/li&gt;
&lt;li&gt;Business rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While DI can technically be used for things like per-request data, frontend context, or React hooks — I’ve found a few constraints that work consistently well in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚙️ Scoped behaviors are better passed as function parameters to long-lived services.&lt;/li&gt;
&lt;li&gt;🧱 Avoid stateful scoped behavior when possible — represent it as dry data.&lt;/li&gt;
&lt;li&gt;🧩 If unavoidable, contain scoped state behind clear behavioral boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using a DI framework, I avoid injecting scoped or contextual values into factory functions or constructors. Instead:&lt;/p&gt;

&lt;p&gt;I deliberately keep DI focused on constructing long-lived behavioral components,&lt;br&gt;
and pass context-specific values as arguments to their methods —&lt;br&gt;
even if that means wiring things manually.&lt;/p&gt;

&lt;p&gt;This separation of concerns keeps my boundaries clean, and avoids ambiguity about where state lives or how it's scoped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Side Effects
&lt;/h3&gt;

&lt;p&gt;Apps have side effects. They need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log things&lt;/li&gt;
&lt;li&gt;Call APIs&lt;/li&gt;
&lt;li&gt;Persist data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all side effects — and they’re essential. Trying to remove them entirely is impractical. But what we can do is structure them intentionally.&lt;/p&gt;

&lt;p&gt;That’s where DI fits in:&lt;/p&gt;

&lt;p&gt;Dependency Injection doesn’t eliminate side effects — it isolates them.&lt;br&gt;
It gives them clear entry points, makes them swappable, and keeps them from leaking into pure logic.&lt;/p&gt;

&lt;p&gt;This isn't in conflict with functional principles — it's complementary.&lt;/p&gt;

&lt;p&gt;Functional core, imperative shell — and DI lives in the shell.&lt;/p&gt;

&lt;p&gt;By pushing side-effectful behavior to the edges of your system, and injecting it explicitly into behavioral units, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More predictable flows&lt;/li&gt;
&lt;li&gt;Cleaner separation of concerns&lt;/li&gt;
&lt;li&gt;Easier testability without mocks or global stubbing&lt;/li&gt;
&lt;li&gt;Even if you’re not writing "pure functional code," DI helps keep impurity structured, not scattered.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  DI and Testing: You Get Control
&lt;/h3&gt;

&lt;p&gt;Once you’ve drawn clear boundaries between behavior and side effects, testing becomes a lot easier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can replace side-effectful parts with test doubles.&lt;/li&gt;
&lt;li&gt;You can isolate core behavior without mocks or globals.&lt;/li&gt;
&lt;li&gt;You can preload or stub behavior deterministically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is knowing where the impurity lives — and keeping it out of the core logic.&lt;/p&gt;

&lt;p&gt;It’s not about mocking everything — it’s about not needing to mock most things (if not all).&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>dependencyinjection</category>
      <category>architecture</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Internal State: Not Evil, Just Misplaced</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Tue, 15 Apr 2025 18:48:21 +0000</pubDate>
      <link>https://forem.com/myungsunrex/internal-state-not-evil-just-misplaced-20dh</link>
      <guid>https://forem.com/myungsunrex/internal-state-not-evil-just-misplaced-20dh</guid>
      <description>&lt;p&gt;State is everywhere. And that’s not a problem — unless you pretend it isn’t.&lt;/p&gt;

&lt;p&gt;In real-world systems, especially those that span multiple scopes like backend applications, state is inevitable. The key isn’t to eliminate it — it’s to &lt;strong&gt;manage it deliberately&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While building a new dependency injection library for TypeScript called TypeWire, I applied the same architectural principles I’ve long followed — particularly around &lt;strong&gt;clarity in boundaries and ownership of state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article shares lessons learned from that experience, using the internal evolution of TypeWire to illustrate how state can be structured, isolated, and owned with confidence — without becoming ambient or unpredictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Internal State in Practice
&lt;/h2&gt;

&lt;p&gt;I’ve been crafting a project called &lt;a href="https://github.com/typewirets/typewirets" rel="noopener noreferrer"&gt;TypeWire&lt;/a&gt;, a DI library for TypeScript, and along the way I’ve learned a lot about designing internal state with strong boundaries.&lt;/p&gt;

&lt;p&gt;TypeWire started with a simple container class that handled everything — bindings, singleton caches, and resolution state — all internally.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TypeWireContainer&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ResolutionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BindingContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;bindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypeWire&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resolutions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;typeSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypeSymbol&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Resolution logic lived here&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;p&gt;This worked until I began hitting concurrency issues during parallel resolutions, particularly false positives in the circular dependency monitor. This pushed me toward isolating resolution per request.&lt;/p&gt;

&lt;p&gt;To fix that, I created a &lt;code&gt;ScopedResolutionContext&lt;/code&gt; — an ephemeral context that owns each resolution lifecycle:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScopedResolutionContext&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ResolutionContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypeWire&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resolutionMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResolutionMonitor&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;p&gt;This made things easier to reason about and brought clear ownership to resolution-time behavior. But resolution-specific &lt;strong&gt;mutable state&lt;/strong&gt; still lived in this class.&lt;/p&gt;

&lt;p&gt;So I extracted that too — into &lt;code&gt;ResolutionState&lt;/code&gt;, whose sole job is to track in-flight data during resolution.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResolutionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resolutions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;p&gt;This gave me three clear layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TypeWireContainer&lt;/code&gt;: orchestrates the lifecycle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ScopedResolutionContext&lt;/code&gt;: owns per-resolution behavior&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ResolutionState&lt;/code&gt;: holds mutable state, tightly scoped and invisible elsewhere&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These decisions were made iteratively — each step clarified boundaries and reduced ambiguity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State can be refined gradually&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ownership boundaries can evolve toward clarity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Internal structure matters as much as public API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A well-abstracted public API makes this evolution possible — since behavior remains stable while internals improve&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Scoped State and Behavioral Boundaries
&lt;/h2&gt;

&lt;p&gt;Short-lived or request-bound state can be tricky to manage — especially when it spreads into shared objects or global modules.&lt;/p&gt;

&lt;p&gt;In my own work, I try to &lt;strong&gt;construct state on demand&lt;/strong&gt; and ensure it’s owned by a tightly scoped boundary.&lt;/p&gt;

&lt;p&gt;In the same system, transient resolution data — like in-flight dependency graphs — is encapsulated inside a &lt;code&gt;ResolutionState&lt;/code&gt; class. This class lives only during resolution, and is owned by a &lt;code&gt;ScopedResolutionContext&lt;/code&gt; wrapper:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScopedResolutionContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resolutionState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResolutionState&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;p&gt;This pattern reflects a broader principle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal state isn't just hidden — it's &lt;strong&gt;delegated further&lt;/strong&gt; to the layer that truly owns its lifecycle&lt;/li&gt;
&lt;li&gt;Mutability is minimized and localized&lt;/li&gt;
&lt;li&gt;Behavior is centralized around one interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I work with contextual and temporary state, I try to give it a dedicated layer — and let behavior encapsulate its usage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Long-Lived vs Contextual State
&lt;/h2&gt;

&lt;p&gt;Some state lives for the lifecycle of the application (e.g., configuration caches, connection pools). Others live per request, per resolution, or per session.&lt;/p&gt;

&lt;p&gt;Issues can emerge when long-lived objects inadvertently hold onto short-lived context.&lt;/p&gt;

&lt;p&gt;In our example, singleton values are stored only once and safely. Meanwhile, contextual logic — like what’s needed during one resolution flow — is dynamically built per call and discarded afterward.&lt;/p&gt;

&lt;p&gt;The structure makes ownership obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global state lives in a global container&lt;/li&gt;
&lt;li&gt;Resolution-specific state lives inside &lt;code&gt;ResolutionState&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Nothing is ambient, and nothing crosses boundaries without intent&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preventing Scope Bleed
&lt;/h2&gt;

&lt;p&gt;Architecture fails when context-sensitive state (like user identity or feature flags) leaks into shared objects.&lt;/p&gt;

&lt;p&gt;Even with a container, this can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A singleton accesses a value that changes every request&lt;/li&gt;
&lt;li&gt;Transient logic is accidentally shared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What helps in these situations isn't a tool — it’s careful structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass context explicitly&lt;/li&gt;
&lt;li&gt;Use factory functions for delayed evaluation&lt;/li&gt;
&lt;li&gt;Avoid injecting context into globally-shared constructs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The container pattern can help, but only if the boundaries are respected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Encapsulation and Ownership
&lt;/h2&gt;

&lt;p&gt;State is not the enemy. &lt;strong&gt;Unowned state is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What makes the TypeWire container safe is not the lack of state — it’s the clarity of ownership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The container owns global state&lt;/li&gt;
&lt;li&gt;The resolution context owns transient state&lt;/li&gt;
&lt;li&gt;No one else touches either&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In your own system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose who owns the cache&lt;/li&gt;
&lt;li&gt;Decide where request-scoped values live&lt;/li&gt;
&lt;li&gt;Expose behavior — not state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a &lt;code&gt;Map&lt;/code&gt; or a flag is being read and written by multiple layers, I consider it no longer internal. That’s when I try to wrap it, abstract it, and assign clear ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reflection: Does This Cover It?
&lt;/h2&gt;

&lt;p&gt;Let’s recap what good internal state management looks like — and whether our example matches up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;Reflected in TypeWire?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scoped state is owned&lt;/td&gt;
&lt;td&gt;✅ ScopedResolutionContext + ResolutionState&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-lived state is isolated&lt;/td&gt;
&lt;td&gt;✅ Singletons live in the container only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing is ambient or implicit&lt;/td&gt;
&lt;td&gt;✅ No global context injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access is through behavior&lt;/td&gt;
&lt;td&gt;✅ All state hidden behind &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;bind&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutable state is not shared&lt;/td&gt;
&lt;td&gt;✅ Encapsulated in internal maps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contextual resolution is explicit&lt;/td&gt;
&lt;td&gt;✅ Built per-call, never reused&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even internal constructs like &lt;code&gt;ResolutionState&lt;/code&gt; are a reminder that &lt;strong&gt;internal state can benefit from layered ownership — and I’ve found that layering it often helps clarify intent.&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;State isn’t evil. But pretending your system doesn’t have any — or letting it sprawl uncontrolled — is.&lt;/p&gt;

&lt;p&gt;In TypeWire, we embraced internal state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Singleton caches exist — but behind a behavioral interface&lt;/li&gt;
&lt;li&gt;Transient resolution is scoped — not ambient&lt;/li&gt;
&lt;li&gt;Contextual state is passed or constructed — never injected blindly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn’t solved all at once. These boundaries — and the state ownership behind them — were clarified over time. Each change made the next one easier, precisely because earlier decisions respected separation of concerns.&lt;/p&gt;

&lt;p&gt;When internal state is well-scoped and behaviorally owned, it enables iterative improvements without ripple effects.&lt;/p&gt;

&lt;p&gt;The result is a system that uses state effectively, safely, and transparently.&lt;/p&gt;

&lt;p&gt;And that’s the goal for any architecture: not to be stateless, but to be &lt;strong&gt;clear about where state lives, who owns it, and what it supports.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>softwaredesign</category>
    </item>
    <item>
      <title>Part 2 – Constructing Boundaries (Not Just Using Classes)</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Thu, 10 Apr 2025 17:18:13 +0000</pubDate>
      <link>https://forem.com/myungsunrex/part-2-constructing-boundaries-not-just-using-classes-5gh9</link>
      <guid>https://forem.com/myungsunrex/part-2-constructing-boundaries-not-just-using-classes-5gh9</guid>
      <description>&lt;p&gt;The debate over classes vs functions is one of the internet's favorite distractions. But in practice, the real question isn’t &lt;em&gt;“OOP vs FP”&lt;/em&gt; — it’s &lt;strong&gt;“How do we define and construct clear boundaries around behavior and dependencies?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn’t a debate about keywords — it’s a conversation about &lt;strong&gt;intentional construction&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 Constructed Boundaries &amp;gt; Singleton Modules
&lt;/h2&gt;

&lt;p&gt;I often see patterns where modules default-export ambient objects:&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="c1"&gt;// logger.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;p&gt;As I’ve worked in larger-scale codebases, I tend to avoid this approach. Instead, I prefer to define a constructor — either a function or class — as a way to &lt;strong&gt;defer construction&lt;/strong&gt; and inject dependencies.&lt;/p&gt;

&lt;p&gt;This style allows clearer boundaries around configuration and collaborators, leading to more testable and maintainable systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 Composable Construction
&lt;/h2&gt;

&lt;p&gt;Let’s start with a simple, practical example of dependency injection — a logger that receives configuration as a parameter, not from a global module or static value.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&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="s2"&gt;zod&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LoggerConfigSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LoggerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;LoggerConfigSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LogLevelDebug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LogLevelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LogLevelWarn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LogLevelError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, here are two ways to define a logger that consumes this config:&lt;/p&gt;

&lt;p&gt;Both of the following examples define an explicit, testable, dependency-injected logger — one with a class, one with a function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class-Based Logger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleLogger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggerConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;LogLevelInfo&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&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="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&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="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LogLevelInfo&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&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;log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LogLevelError&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&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;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&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="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&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="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&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="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Factory-Based Logger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLogger&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="nx"&gt;LoggerConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &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="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;LogLevelInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`[&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="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LogLevelInfo&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LogLevelError&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&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="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&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="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&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="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Both are great — because both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are constructed explicitly&lt;/li&gt;
&lt;li&gt;Accept config and collaborators&lt;/li&gt;
&lt;li&gt;Expose a clean, testable public API&lt;/li&gt;
&lt;li&gt;Allow the &lt;strong&gt;consumer&lt;/strong&gt; to decide whether the instance should be singleton, transient, or context-bound&lt;/li&gt;
&lt;li&gt;Enable environment-specific wiring of dependencies rather than hardwiring them through static linkage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This flexibility is especially valuable in testing and modular architectures. And despite what it might look like at a glance — setting up these patterns doesn’t take much longer than writing the static version.&lt;/p&gt;

&lt;p&gt;In fact, many engineers agree with the idea of composition and clean separation — yet we often spend more time debating whether it’s too much boilerplate than it would take to actually implement it. Setting up patterns like these — a simple constructor, an injectable utility, a boundary-aware service — typically takes no more than 5–10 minutes each, and even less as you get more fluent with the pattern. This isn't extra ceremony; it's optionality you can afford. It's a small investment that pays off in flexibility, clarity, and ease of change — especially as your system grows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🧩 And while only one of them uses the &lt;code&gt;class&lt;/code&gt; keyword, &lt;strong&gt;both are conceptually defining a class&lt;/strong&gt;. The presence of &lt;code&gt;new&lt;/code&gt; or &lt;code&gt;prototype&lt;/code&gt; isn’t what matters. What matters is &lt;strong&gt;the boundary&lt;/strong&gt; — and whether you construct it cleanly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Personally, I prefer using &lt;code&gt;class&lt;/code&gt; for most of my production code. I find it helps clearly separate dependencies, internal state, and external behavior. It also allows me to group private helpers in a natural way, and IDEs tend to provide better support — from navigation to inline documentation — when using classes.&lt;/p&gt;

&lt;p&gt;Now let’s revisit the idea of configuration itself being injected — not globally loaded.&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="c1"&gt;// config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;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;private&lt;/span&gt; &lt;span class="nf"&gt;getAnyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&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;keySize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;keySize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;keySize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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;return&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty key is not allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAnyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;getSlice&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty key is not allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAnyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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="c1"&gt;// config-loader.jsonc.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadJsoncConfig&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;configPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config.jsonc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// This could also be injected. We're leaving it hardcoded for this example to keep the focus on&lt;/span&gt;
  &lt;span class="c1"&gt;// demonstrating how different parts can be composed and swapped later.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSONC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;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;p&gt;Finally, here’s how we wire that config service into our logger:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&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;rawConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadJsoncConfig&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;configService&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;ConfigService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// This separation makes it easy to swap different file formats or config loading mechanisms —&lt;/span&gt;
  &lt;span class="c1"&gt;// whether it's JSON, env vars, remote endpoints, or CLI flags.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawLoggerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logger&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;loggerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LoggerConfigSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawLoggerConfig&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;logger&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;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loggerConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


  &lt;span class="c1"&gt;// continue application setup...&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g., pass logger into your server, router, or DI container&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This highlights the pattern: you can &lt;strong&gt;defer construction&lt;/strong&gt;, inject dependencies, and compose behavior cleanly — without relying on global state or static linkage.&lt;/p&gt;

&lt;p&gt;This isn’t just a pattern for enterprise-scale systems. Even in small prototypes, defining boundaries early makes it easier to stub things, swap implementations, or integrate with evolving environments. The upfront cost is low — and the downstream flexibility is real.&lt;/p&gt;

&lt;p&gt;Here’s a quick comparison of the two approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Singleton / Ambient Module&lt;/td&gt;
&lt;td&gt;Shared instance via global import&lt;/td&gt;
&lt;td&gt;Simple, fast for small projects&lt;/td&gt;
&lt;td&gt;Hard to test, inflexible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constructed Component&lt;/td&gt;
&lt;td&gt;Built via constructor or factory, passed explicitly&lt;/td&gt;
&lt;td&gt;Composable, testable, modular — even in prototypes&lt;/td&gt;
&lt;td&gt;Slightly more setup upfront (usually 5–10 mins max, or instance if you have a good vibe😉)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ☕ Java &amp;amp; Go Comparison: You're Already Doing This
&lt;/h2&gt;

&lt;p&gt;Before we dive into the structured, interface-based versions, it’s worth noting that Java and Go also have ambient-style patterns — the equivalent of a default-exported singleton in JavaScript:&lt;/p&gt;

&lt;h3&gt;
  
  
  Java – Static Logger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StaticLogger&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;log&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;msg&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go – Package-Level Function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&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;p&gt;These work for small programs, but they tend to leak dependencies and make configuration or testing harder — much like ambient modules in JavaScript.&lt;/p&gt;

&lt;p&gt;If you're writing Java, you're already familiar with this pattern:&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;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;log&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;msg&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="nf"&gt;withContext&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;ctx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleLogger&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ConsoleLogger&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;prefix&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;log&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;msg&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"["&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"] "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="nf"&gt;withContext&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;ctx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Go, you'd write something very similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ConsoleLogger&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Prefix&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[%s] %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ctx&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;p&gt;In both cases, you're &lt;strong&gt;constructing with dependencies&lt;/strong&gt;, and explicitly wiring in behavior. You avoid ambient state and expose a small surface area for consumers.&lt;/p&gt;

&lt;p&gt;You never default-export a global &lt;code&gt;Logger&lt;/code&gt; instance in Java or Go. You &lt;strong&gt;construct&lt;/strong&gt;, inject, and isolate. That’s the same idea we’re advocating here — just with different syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚪 A Word on Object-Oriented Discipline
&lt;/h2&gt;

&lt;p&gt;As complexity grows, classes can offer some ergonomic benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grouping behavior and helpers using &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;#&lt;/code&gt; methods&lt;/li&gt;
&lt;li&gt;Better IDE discoverability and navigation&lt;/li&gt;
&lt;li&gt;Easier organization of lifecycle-bound internal state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it’s important not to over-apply OOP conventions. I generally &lt;strong&gt;avoid subclassing&lt;/strong&gt; and prefer composition over inheritance — not because inheritance is inherently wrong, but because it often introduces tight coupling and fragile hierarchies. When behavior needs to be shared, I favor helpers, delegates, or injected collaborators.&lt;/p&gt;

&lt;p&gt;Likewise, I avoid &lt;code&gt;protected&lt;/code&gt; methods. I find it cleaner to stick with &lt;code&gt;public&lt;/code&gt; and &lt;code&gt;private&lt;/code&gt;, keeping the object interface clear and the internals encapsulated.&lt;/p&gt;

&lt;p&gt;The takeaway here isn’t that &lt;em&gt;classes win&lt;/em&gt; — it’s that &lt;strong&gt;clarity wins&lt;/strong&gt;. Whether you’re using class syntax or a factory function, the important part is that you’re being deliberate about boundaries and dependencies.&lt;/p&gt;

&lt;p&gt;And once again, the key isn't the &lt;code&gt;class&lt;/code&gt; keyword — it's the pattern of construction.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Conclusion: Construct, Don’t Just Declare
&lt;/h2&gt;

&lt;p&gt;Use a class. Use a factory. Use a struct in Go or a POJO in Java.&lt;/p&gt;

&lt;p&gt;The real question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are you constructing your boundaries, or leaking them via ambient state?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s what makes your codebase adaptable — not the presence of &lt;code&gt;class&lt;/code&gt;, but the presence of intention. (Or &lt;code&gt;struct&lt;/code&gt;, if you're using Go. Or &lt;code&gt;table&lt;/code&gt;, if you're writing Lua 😉)&lt;/p&gt;

&lt;p&gt;Start small. Inject later. Boundaries give you leverage.&lt;/p&gt;

&lt;p&gt;This isn’t about trying to predict every possible future feature — it’s about shaping your code so that the behavior you define is easily composable, and composed behaviors are much easier to reason about. There’s a meaningful difference between designing for flexibility and overengineering for speculation.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>dependencyinjection</category>
      <category>softwaredesign</category>
    </item>
    <item>
      <title>Clarity in Architecture: Not OOP vs FP</title>
      <dc:creator>Rex</dc:creator>
      <pubDate>Wed, 09 Apr 2025 17:15:56 +0000</pubDate>
      <link>https://forem.com/myungsunrex/clarity-in-architecture-not-oop-vs-fp-2mhf</link>
      <guid>https://forem.com/myungsunrex/clarity-in-architecture-not-oop-vs-fp-2mhf</guid>
      <description>&lt;h2&gt;
  
  
  Clarity in Architecture: Not OOP vs FP
&lt;/h2&gt;

&lt;p&gt;As a polyglot full stack engineer who's worked across both frontend and backend — in Java, Kotlin, Python, Go, JavaScript, TypeScript, Ruby, Lua, and PHP — I’ve worked through both fully object-oriented and fully functional phases in my career — and I’ve found that architecture debates often miss the real problem. The tension isn’t between paradigms. It’s between clarity and ambiguity. Too often, discussions get stuck in ideological traps — OOP means classes, DI is bad, FP isn’t practical, SOLID is sacred — or on the other end of the spectrum, some advocate for having no structure at all. These positions end up dominating the conversation instead of leading to a productive exchange.&lt;/p&gt;

&lt;p&gt;The best systems I built — or helped evolve — weren’t purely object-oriented or strictly functional. They were clear. Their boundaries made sense. And once those boundaries were in place, it became much easier to reason about the system, understand how everything fit together, and extend it without fear of unexpected consequences.&lt;/p&gt;

&lt;p&gt;This first article kicks off a series that doesn’t argue for a paradigm — it argues for architectural clarity. It’s about how we reason through systems, how we approach problems, and how we make structural decisions that help code grow cleanly. I’m not offering a universal formula — just sharing patterns and principles that have consistently helped me evolve real systems with less friction and more confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Question: Where Does This Go?
&lt;/h2&gt;

&lt;p&gt;Forget OOP vs FP — or any single paradigm. What I kept running into was the difficulty of seeing the big picture when small decisions piled up. The real challenge is simplifying the system enough to reason about it clearly — and that often starts with asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Where does this code belong?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A factory function? A class? A context object? A singleton? A reducer?&lt;/p&gt;

&lt;p&gt;When we make the wrong call, it spreads ambiguity: unclear ownership, unpredictable effects, and brittle testability. When we get it right, the system flows.&lt;/p&gt;

&lt;p&gt;So instead of taking sides in OOP vs FP, this series focuses on practical boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When does a class help?&lt;/li&gt;
&lt;li&gt;When is internal state okay?&lt;/li&gt;
&lt;li&gt;What belongs in dependency injection — and what doesn’t?&lt;/li&gt;
&lt;li&gt;How do you keep functional principles in an effectful system?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s reframe the discussion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Classes for Behavior
&lt;/h2&gt;

&lt;p&gt;The word “class” triggers a lot of baggage — inheritance, over-engineering, Java boilerplate. But at its core, a class (or in Go, a struct paired with interfaces) is just a way to encapsulate &lt;strong&gt;behavior with shared internal logic&lt;/strong&gt;. I emphasize composition over inheritance — the value of classes comes not from hierarchies, but from the ability to clearly group related behavior.&lt;/p&gt;

&lt;p&gt;Use a class when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have multiple methods that coordinate shared state or logic&lt;/li&gt;
&lt;li&gt;You need a clear lifecycle or ownership boundary&lt;/li&gt;
&lt;li&gt;You want to separate dependency construction from behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re just grouping utilities or data&lt;/li&gt;
&lt;li&gt;There’s no internal cohesion&lt;/li&gt;
&lt;li&gt;You only need a couple of flat methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have persistent data&lt;/strong&gt; that belongs in a database, API, or external system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Classes make it easy to separate the act of wiring dependencies (in the constructor) from the internal business logic (in the methods).&lt;/p&gt;

&lt;p&gt;Personally, I avoid storing long-lived data in classes. Instead, I treat them as coordinators — not sources of truth. When I need to persist data like user info or logs, I reach for a proper database, API, or storage layer.&lt;/p&gt;

&lt;p&gt;I’m more interested in how classes compose than how they inherit — and I often pair them with interfaces to keep things flexible. We’ll unpack that further in the next part of the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal State: Not Evil, Just Misplaced
&lt;/h2&gt;

&lt;p&gt;Functional programming taught us to fear state. But the truth is, &lt;strong&gt;state is fine&lt;/strong&gt; — &lt;em&gt;when it’s owned&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The key is &lt;strong&gt;cohesion&lt;/strong&gt;. State should live in objects that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have a clear lifecycle&lt;/li&gt;
&lt;li&gt;Own their behavior&lt;/li&gt;
&lt;li&gt;Don’t leak into the rest of the system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are managing ephemeral data&lt;/strong&gt;, not persistent records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In game development, for example, many objects manage both internal state and behavior tied to a well-defined lifecycle — a natural fit for class-based encapsulation.&lt;/p&gt;

&lt;p&gt;Ephemeral or short-lived data belongs inside classes that directly control that lifecycle. Long-lived or persistent data should reside in a database, cache, or external API. Classes can read and write that data, but shouldn’t try to own or retain it.&lt;/p&gt;

&lt;p&gt;Contextual values — like session data, request scope, or frontend state — are also better constructed where they’re needed. They’re often tied to a specific user, time, or flow, and don’t belong in global DI containers. Treating them as contextual inputs keeps the boundaries clean and avoids accidental state leaks.&lt;/p&gt;

&lt;p&gt;We’ll dive into practical examples of these patterns in the next part of the series.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dependency Injection Should Be Explicit
&lt;/h2&gt;

&lt;p&gt;Some might argue that global singletons are fine for things like config files, loggers, or telemetry clients. After all, they’re used everywhere and often initialized once. But in practice, they’re rarely as simple or harmless as they seem.&lt;/p&gt;

&lt;p&gt;These so-called singletons almost always involve side effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Config&lt;/strong&gt; often pulls from dynamic environments or remote sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; requires runtime setup, formatting, and transports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature flags&lt;/strong&gt; often start as a simple in-memory key-value store, but over time evolve into complex rule engines that evaluate conditions dynamically — sometimes even requiring server-side calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry clients&lt;/strong&gt; rely on side-loaded credentials and context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wiring dependencies manually — whether via DI frameworks or localized injection — makes them explicit and traceable. You can see what’s being used, where, and why. This isn’t about evangelizing any one tool — it’s about reinforcing clarity and testability through visibility.&lt;/p&gt;

&lt;p&gt;Dependency Injection (DI) tends to provoke strong reactions — it's either misused as a magic context grab-bag, or religiously avoided altogether. But when used intentionally, DI gives you a &lt;strong&gt;system-wide service map&lt;/strong&gt;, not an ambient variable soup.&lt;/p&gt;

&lt;p&gt;Use DI for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long-lived shared services (Logger, DB, API clients)&lt;/li&gt;
&lt;li&gt;Config or feature flags&lt;/li&gt;
&lt;li&gt;Composable business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid DI for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anything contextual (Session data, request-specific data)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good way to think about DI is as a system-wide service map — not a place to store request- or user-specific context.&lt;/p&gt;

&lt;p&gt;The rule of thumb: inject &lt;strong&gt;services&lt;/strong&gt;, construct &lt;strong&gt;context&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Functional Principles Still Matter
&lt;/h2&gt;

&lt;p&gt;The best systems I worked on still lean heavily on functional ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immutable definitions&lt;/li&gt;
&lt;li&gt;Composability&lt;/li&gt;
&lt;li&gt;Strong typing&lt;/li&gt;
&lt;li&gt;Isolation of effects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But real-world applications need &lt;strong&gt;side effects&lt;/strong&gt; — logging, config loading, HTTP calls, and more. DI and OOP give you structure around those effects without making the system impure &lt;em&gt;everywhere&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The goal isn’t functional purity — it’s having clear, controlled side effects with minimal confusion about where they happen and why.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This series is about clarity in architecture — not purity, not paradigm loyalty. If you’ve ever felt unsure where a piece of logic belongs, or struggled to keep code testable and understandable as it grows, this series is for you.&lt;/p&gt;

&lt;p&gt;Next up: we’ll look deeper at when to use a class, when not to, and how to make the boundary between dependencies and behavior sharp.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>typescript</category>
      <category>softwareengineering</category>
      <category>di</category>
    </item>
  </channel>
</rss>
