<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>cyx</title>
    <link>https://cyx.writeas.com/</link>
    <description></description>
    <pubDate>Tue, 21 Apr 2026 12:58:16 +0000</pubDate>
    <item>
      <title>Dependency Injection in Go</title>
      <link>https://cyx.writeas.com/heading?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[When I mention dependency injection to folks who have Java or .NET backgrounds it usually invokes a few sets of questions -- to which my usual response is:&#xA;&#xA;  It&#39;s likely not as sophisticated as what you&#39;re thinking -- and it doesn&#39;t have to be!&#xA;&#xA;While there are methods to use more sophisticated approaches in Go, starting off projects the simplest way possible is still the preferred approach.&#xA;&#xA;[wire]: https://github.com/google/wire&#xA;&#xA;Definitions&#xA;&#xA;Dependency injection represents the D in SOLID principles. Quoting from wikipedia:&#xA;&#xA;  In object-oriented computer programming, SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable.&#xA;&#xA;Why dependency injection is important in practice?&#xA;&#xA;Let&#39;s use a hypothetical application which is somewhat like a lambda: essentially, we allow customers to store their code, and we can run them in some sort of containerized environment. In this product, our ubiquitious language might have the following entities:&#xA;&#xA;Code - represents a customer&#39;s arbitrary code.&#xA;&#xA;In addition, we might require the following service abstractions:&#xA;&#xA;Repository - provides an abstraction of data.&#xA;Runtime -  allows you to execute arbitrary code.&#xA;&#xA;And a classical router, controller, or command layer to receive requests and act on them:&#xA;&#xA;Server - can be considered as the Command layer as in CQRS.&#xA;&#xA;Sketching a minimal example of this:&#xA;&#xA;package api&#xA;&#xA;type Server struct {&#xA;        Repo Repository&#xA;        Runtime Runtime&#xA;}&#xA;&#xA;func (s Server) ExecuteCode(ctx context.Context, req ExecuteRequest) (ExecuteResponse, error) {&#xA;        code, err := s.Repo.FetchCode(ctx, req.ID)&#xA;        if err != nil {&#xA;                return nil, err&#xA;        }&#xA;&#xA;        res, err := s.Runtime.Execute(ctx, code)&#xA;        if err != nil {&#xA;                return nil, err&#xA;        }&#xA;&#xA;        return &amp;ExecuteResponse{Payload: res.Payload}&#xA;}&#xA;&#xA;Playing out what tech we&#39;ll use&#xA;&#xA;If we stick to boring technology:&#xA;&#xA;Repository - will likely just be backed by postgres.&#xA;Runtime - we can explore using lambda (or openwhisk, cloudflare workers, the list goes on).&#xA;&#xA;How will we test these?&#xA;&#xA;If we want to have a good testing experience, we&#39;ll strive for the&#xA;following:&#xA;&#xA;just works without any dependencies at all.&#xA;go test ./...&#xA;&#xA;does integration testing using env vars or flags.&#xA;env $(cat .env) go test /... -tags integration&#xA;&#xA;[testing]: https://peter.bourgon.org/go-in-production/#testing-and-validation&#xA;&#xA;So what does this mean if we want to be able to test without any postgres dependency? Likely it means we either don&#39;t test the postgres implementation of the Repository at all. That&#39;s probably fine, but then would we also skip testing the Server layer because it depends on Repository?&#xA;&#xA;Similarly, if our Runtime implementation relies on lambda, how will we do testing with that?&#xA;&#xA;This is where interfaces, and good design helps!&#xA;&#xA;It&#39;s all about the interface&#xA;&#xA;If we changed the code above to instead rely on an interface:&#xA;&#xA;package api&#xA;&#xA;type Repository interface {&#xA;        FetchCode(ctx context.Context, id string) (Code, error)&#xA;}&#xA;&#xA;type Runtime interface {&#xA;        Execute(ctx context.Context, Code) (RuntimeResponse, error)&#xA;}&#xA;&#xA;type Server struct {&#xA;        Repo Repository&#xA;        Runtime Runtime&#xA;}&#xA;&#xA;Then we can at least test the Server layer by providing a mock implementation for our dependency-less testing.&#xA;&#xA;We can then do -tags integration style testing in CI to provide a real database, and a test AWS environment for us to interact with lambda so we&#39;re guaranteed that any change we do hits the real thing all the time -- but we still empower developers with a faster feedback loop on average.&#xA;&#xA;So what about dependency injection?&#xA;&#xA;Ultimately, because we&#39;ve designed our system to take in well defined services instead of concrete implementations, we can simply change how we initialize our main file:&#xA;&#xA;func main() {&#xA;        var cfg config&#xA;        if err := envdecode.StrictDecode(&amp;cfg); err != nil {&#xA;                log.Fatal(err)&#xA;        }&#xA;&#xA;        ctx := contextWithSignals(os.Interrupt)&#xA;       if err := run(ctx, cfg); err != nil {&#xA;                log.Fatal(err)&#xA;        }&#xA;}&#xA;&#xA;func run(ctx context.Context, cfg config) error {&#xA;        db, err := sql.Open(cfg.DatabaseURL)&#xA;        if err != nil {&#xA;                return err&#xA;        }&#xA;&#xA;        repository, err := postgres.NewRepository(db)&#xA;        if err != nil {&#xA;                return err&#xA;        }&#xA;&#xA;        runtime, err := lambda.NewRuntime(cfg.AWS.Key, cfg.AWS.Secret)&#xA;        if err != nil {&#xA;                return err&#xA;        }&#xA;&#xA;        server := &amp;api.Server{&#xA;                Repository: repository,&#xA;                Runtime: runtime,&#xA;        }&#xA;&#xA;        return server.Run()&#xA;}&#xA;&#xA;And then in our test, we can imagine providing all the mock / stub&#xA;implementations as necessary.&#xA;&#xA;Will we end up writing a lot of boilerplate mock code?&#xA;&#xA;Probably yes -- and it really depends how much of your domain requires very heavy dependencies.&#xA;&#xA;If your domain is really just interacting with a database -- maybe you can argue that in order for your devs to work with the code, a local postgres must be available (or one in docker) -- and that&#39;s fine! Ultimately it&#39;s all about tradeoffs.&#xA;&#xA;Once the set of dependencies become a bit too heavy though, we&#39;d want to keep the developer feedback loop as fast as possible, while answering the following questions:&#xA;&#xA;How much of the project business logic involves these heavy dependencies? Is it 10%? 50%?&#xA;How often do developers modify these external heavy dependencies? (In the&#xA;   example we have, how much of the work becomes modifying lambda code, vs&#xA;   modifying postgres code, vs modifying the server code?)&#xA;&#xA;As is often the case with software engineering, it&#39;s all about tradeoffs -- and when you need to reach for dependency injection it&#39;s just right there in your toolbelt.&#xA;&#xA;Footnotes&#xA;&#xA;In certain cases, certain companies have even gone and implemented an entire&#xA;  mock implementation for their customers to use. LetsEncrypt shipped&#xA;  pebble which is a miniaturized version of their real service&#xA;  offering.&#xA;When should you reach for sophisticated tools like wire? I personally would&#xA;  never use those tool -- but YMMV again.&#xA;&#xA;[pebble]: https://github.com/letsencrypt/pebble&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>When I mention dependency injection to folks who have Java or .NET backgrounds it usually invokes a few sets of questions — to which my usual response is:</p>

<blockquote><p>It&#39;s likely not as sophisticated as what you&#39;re thinking — and it doesn&#39;t have to be!</p></blockquote>

<p>While there are methods to use more <a href="https://github.com/google/wire" rel="nofollow">sophisticated</a> approaches in Go, starting off projects the simplest way possible is still the preferred approach.</p>

<h2 id="definitions" id="definitions">Definitions</h2>

<p>Dependency injection represents the <code>D</code> in <code>SOLID</code> principles. Quoting from wikipedia:</p>

<blockquote><p>In object-oriented computer programming, SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable.</p></blockquote>

<h2 id="why-dependency-injection-is-important-in-practice" id="why-dependency-injection-is-important-in-practice">Why dependency injection is important in practice?</h2>

<p>Let&#39;s use a hypothetical application which is somewhat like a lambda: essentially, we allow customers to store their code, and we can run them in some sort of containerized environment. In this product, our ubiquitious language might have the following entities:</p>
<ul><li><em>Code</em> – represents a customer&#39;s arbitrary code.</li></ul>

<p>In addition, we might require the following service abstractions:</p>
<ul><li><em>Repository</em> – provides an abstraction of data.</li>
<li><em>Runtime</em> –  allows you to execute arbitrary code.</li></ul>

<p>And a classical router, controller, or command layer to receive requests and act on them:</p>
<ul><li><em>Server</em> – can be considered as the <code>Command</code> layer as in <a href="https://en.wikipedia.org/wiki/Command%E2%80%93query_separation" rel="nofollow">CQRS</a>.</li></ul>

<p>Sketching a minimal example of this:</p>

<pre><code class="language-golang">package api

type Server struct {
        Repo *Repository
        Runtime *Runtime
}

func (s *Server) ExecuteCode(ctx context.Context, req *ExecuteRequest) (*ExecuteResponse, error) {
        code, err := s.Repo.FetchCode(ctx, req.ID)
        if err != nil {
                return nil, err
        }

        res, err := s.Runtime.Execute(ctx, code)
        if err != nil {
                return nil, err
        }

        return &amp;ExecuteResponse{Payload: res.Payload}
}
</code></pre>

<h2 id="playing-out-what-tech-we-ll-use" id="playing-out-what-tech-we-ll-use">Playing out what tech we&#39;ll use</h2>

<p>If we stick to boring technology:</p>
<ul><li>Repository – will likely just be backed by postgres.</li>
<li>Runtime – we can explore using lambda (or openwhisk, cloudflare workers, the list goes on).</li></ul>

<h2 id="how-will-we-test-these" id="how-will-we-test-these">How will we test these?</h2>

<p>If we want to have a <a href="https://peter.bourgon.org/go-in-production/#testing-and-validation" rel="nofollow">good testing experience</a>, we&#39;ll strive for the
following:</p>

<pre><code># just works without any dependencies at all.
go test ./...

# does integration testing using env vars or flags.
env $(cat .env) go test /... -tags integration
</code></pre>

<p>So what does this mean if we want to be able to test without any postgres dependency? Likely it means we either don&#39;t test the postgres implementation of the Repository at all. That&#39;s probably fine, but then would we also skip testing the <code>Server</code> layer because it depends on <code>*Repository</code>?</p>

<p>Similarly, if our <code>Runtime</code> implementation relies on lambda, how will we do testing with that?</p>

<p>This is where interfaces, and good design helps!</p>

<h2 id="it-s-all-about-the-interface" id="it-s-all-about-the-interface">It&#39;s all about the interface</h2>

<p>If we changed the code above to instead rely on an interface:</p>

<pre><code>package api

type Repository interface {
        FetchCode(ctx context.Context, id string) (Code, error)
}

type Runtime interface {
        Execute(ctx context.Context, Code) (RuntimeResponse, error)
}

type Server struct {
        Repo Repository
        Runtime Runtime
}
</code></pre>

<p>Then we can at least test the <code>Server</code> layer by providing a mock implementation for our dependency-less testing.</p>

<p>We can then do <code>-tags integration</code> style testing in CI to provide a real database, and a test AWS environment for us to interact with lambda so we&#39;re guaranteed that any change we do hits the real thing all the time — but we still empower developers with a faster feedback loop on average.</p>

<h2 id="so-what-about-dependency-injection" id="so-what-about-dependency-injection">So what about dependency injection?</h2>

<p>Ultimately, because we&#39;ve designed our system to take in well defined services instead of concrete implementations, we can simply change how we initialize our main file:</p>

<pre><code>func main() {
        var cfg config
        if err := envdecode.StrictDecode(&amp;cfg); err != nil {
                log.Fatal(err)
        }

        ctx := contextWithSignals(os.Interrupt)
       if err := run(ctx, cfg); err != nil {
                log.Fatal(err)
        }
}

func run(ctx context.Context, cfg config) error {
        db, err := sql.Open(cfg.DatabaseURL)
        if err != nil {
                return err
        }

        repository, err := postgres.NewRepository(db)
        if err != nil {
                return err
        }

        runtime, err := lambda.NewRuntime(cfg.AWS.Key, cfg.AWS.Secret)
        if err != nil {
                return err
        }

        server := &amp;api.Server{
                Repository: repository,
                Runtime: runtime,
        }

        return server.Run()
}

</code></pre>

<p>And then in our test, we can imagine providing all the mock / stub
implementations as necessary.</p>

<h2 id="will-we-end-up-writing-a-lot-of-boilerplate-mock-code" id="will-we-end-up-writing-a-lot-of-boilerplate-mock-code">Will we end up writing a lot of boilerplate mock code?</h2>

<p>Probably yes — and it really depends how much of your domain requires very heavy dependencies.</p>

<p>If your domain is really just interacting with a database — maybe you can argue that in order for your devs to work with the code, a local postgres must be available (or one in docker) — and that&#39;s fine! Ultimately it&#39;s all about tradeoffs.</p>

<p>Once the set of dependencies become a bit too heavy though, we&#39;d want to keep the developer feedback loop as fast as possible, while answering the following questions:</p>
<ol><li>How much of the project business logic involves these heavy dependencies? Is it 10%? 50%?</li>
<li>How often do developers modify these external heavy dependencies? (In the
example we have, how much of the work becomes modifying lambda code, vs
modifying postgres code, vs modifying the server code?)</li></ol>

<p>As is often the case with software engineering, it&#39;s all about tradeoffs — and when you need to reach for dependency injection it&#39;s just right there in your toolbelt.</p>

<h2 id="footnotes" id="footnotes">Footnotes</h2>
<ul><li>In certain cases, certain companies have even gone and implemented an entire
mock implementation for their customers to use. LetsEncrypt shipped
<a href="https://github.com/letsencrypt/pebble" rel="nofollow">pebble</a> which is a miniaturized version of their real service
offering.</li>
<li>When should you reach for sophisticated tools like wire? I personally would
never use those tool — but YMMV again.</li></ul>
]]></content:encoded>
      <guid>https://cyx.writeas.com/heading</guid>
      <pubDate>Sat, 14 Nov 2020 06:43:26 +0000</pubDate>
    </item>
  </channel>
</rss>