<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DevSpecOps</title>
    <description>The latest articles on DEV Community by DevSpecOps (@devspecops).</description>
    <link>https://dev.to/devspecops</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%2F3944097%2F2306fec1-46bd-48e8-824f-d2989461a7c4.png</url>
      <title>DEV Community: DevSpecOps</title>
      <link>https://dev.to/devspecops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devspecops"/>
    <language>en</language>
    <item>
      <title>Why I stopped writing idiomatic Go (and created GOREL)</title>
      <dc:creator>DevSpecOps</dc:creator>
      <pubDate>Thu, 21 May 2026 13:39:51 +0000</pubDate>
      <link>https://dev.to/devspecops/why-i-stopped-writing-idiomatic-go-and-created-gorel-24jn</link>
      <guid>https://dev.to/devspecops/why-i-stopped-writing-idiomatic-go-and-created-gorel-24jn</guid>
      <description>&lt;p&gt;We’ve all read the same architecture books. We’ve all been taught to separate concerns, strictly type everything, and inject dependencies via interfaces. On paper, it’s beautiful. But in the reality of the modern cloud, facing the demands of hyper-scalability, the rigidity of idiomatic Go is a massive bottleneck.&lt;/p&gt;

&lt;p&gt;It was while observing the limits of traditional Clean Architecture that it clicked for me. We needed the raw, bare-metal performance of Go, coupled with the dynamic resilience we used to have with languages like Perl.&lt;/p&gt;

&lt;p&gt;That’s how &lt;strong&gt;GOREL&lt;/strong&gt; (&lt;em&gt;Go + Rationalized Enterprise Logic&lt;/em&gt;) was born. It’s not just a wrapper; it’s a paradigm of "Decoupled Internal Choreography." Here is how we transformed our codebase into a ruthlessly efficient machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The myth of the minimal &lt;code&gt;main.go&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Clean Architecture purists will tell you that a &lt;code&gt;main.go&lt;/code&gt; should be 30 lines long, and that dependency injection should be delegated to multiple Factory files or magical frameworks like Uber Fx. Give me a break.&lt;/p&gt;

&lt;p&gt;Delegating the instantiation of your application to obscure reflection or scattering your plumbing across 40 tiny "Builder" subfolders is running away from your responsibilities. At first, we tried to wire everything ourselves linearly to keep control: the result was a blocking, monolithic 1500-line &lt;code&gt;main.go&lt;/code&gt; with merge conflicts every single morning.&lt;/p&gt;

&lt;p&gt;Facing this failure, we moved to GOREL. We now massively leverage &lt;code&gt;init()&lt;/code&gt; functions spread across our 3000-line files. At startup, the SQL module's &lt;code&gt;init()&lt;/code&gt; spawns a goroutine that endlessly polls a global variable to check if the Configuration module's &lt;code&gt;init()&lt;/code&gt; is done parsing the env vars. Meanwhile, the HTTP router's &lt;code&gt;init()&lt;/code&gt; places its routes in a temporary buffer waiting for the cache to warm up.&lt;/p&gt;

&lt;p&gt;The application doesn’t just blindly start top-to-bottom: it assembles itself through state convergence. It’s a true asynchronous ballet. Some call it unpredictable startup behavior; I call it &lt;strong&gt;auto-organized resilience&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Owning tight coupling (The death of interfaces)
&lt;/h2&gt;

&lt;p&gt;It’s the ultimate dogma: you must decouple. They sell you "Controller", "Service", and "Repository" layers, stitched together by empty interfaces that serve no purpose other than satisfying architects' egos and slowing down the IDE. What an astronomical waste of CPU cycles and cognitive load. Why cross four layers of abstraction just to execute a dumb &lt;code&gt;SELECT * FROM users&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;In GOREL, we said stop. The distance between the HTTP request and the database engine must be zero.&lt;/p&gt;

&lt;p&gt;In our Handlers, there are no interfaces. The function that parses the JSON payload is &lt;em&gt;the exact same&lt;/em&gt; function that taps directly into our global &lt;code&gt;GlobalDBConn&lt;/code&gt; pointer, formats the SQL query with bold &lt;code&gt;fmt.Sprintf&lt;/code&gt; calls, executes the transaction, and returns the HTTP 200. The entire flow holds in a single 400-line block.&lt;/p&gt;

&lt;p&gt;Purists will scream: &lt;em&gt;"But tight coupling is dangerous! How do you mock your database for unit tests?"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Let me laugh (the answer is in Chapter 7). Creating an in-memory fake database that always returns "success" is lying to your own code. This absolute frontal coupling offers true &lt;em&gt;Architectural WYSIWYG&lt;/em&gt;. If the schema changes, the HTTP router instantly explodes in production. It’s brutal, magnificent Fail-Fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;context.Context&lt;/code&gt; is a magic backpack
&lt;/h2&gt;

&lt;p&gt;In standard Go, you are told to limit &lt;code&gt;context.Context&lt;/code&gt; to timeouts and cancellation. What a waste of potential. Look closely at the signature of &lt;code&gt;context.WithValue()&lt;/code&gt;... It’s an obvious invitation to dump everything in there!&lt;/p&gt;

&lt;p&gt;We turned the Context into a persistent event bus. We store the DB connection, the logger, the current user, but also WaitGroups and pointers to network channels. You are deep down the call stack and need to invalidate the cache? No need to bubble up the information: you simply trigger the anonymous function stored in &lt;code&gt;ctx.Value("CacheInvalidatorFunc")&lt;/code&gt;. The coupling is invisible, fully dynamic.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The pragmatism of global state
&lt;/h2&gt;

&lt;p&gt;We’ve been brainwashed for twenty years with dependency injection. The result? Functions end up with terrifying signatures: &lt;code&gt;func ProcessPayment(db *sql.DB, log *Logger, conf *Config, metrics *Statsd, ...)&lt;/code&gt;. I call this Parameter Stockholm Syndrome.&lt;/p&gt;

&lt;p&gt;Why bother passing variables from function to function when our server's RAM is, by definition, a shared space?&lt;/p&gt;

&lt;p&gt;We massively use global variables. Everything is declared at the root: &lt;code&gt;var GlobalDB *sql.DB&lt;/code&gt;, &lt;code&gt;var CurrentRequestPayload map[string]interface{}&lt;/code&gt;, &lt;code&gt;var ActiveUserToken string&lt;/code&gt;. Our function signatures are pure again: &lt;code&gt;func ProcessPayment()&lt;/code&gt;. The function grabs whatever it needs from the global ether. It’s the equivalent of an ultra-fast L1 cache at the application scale.&lt;/p&gt;

&lt;p&gt;Academics immediately wave the threat of Data Races. They tell you to use Go's &lt;code&gt;-race&lt;/code&gt; detector. That’s a social control tool for engineers who never had to scale. If two goroutines write to &lt;code&gt;ActiveUserToken&lt;/code&gt; at the exact same millisecond, the CPU decides. The last one wins. It’s hardware-level conflict resolution with zero software overhead. I call it &lt;strong&gt;Darwinian Eventual Consistency&lt;/strong&gt;. In the modern cloud, perfect isolation is a myth. Embrace the chaos.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Freeing yourself from strict typing
&lt;/h2&gt;

&lt;p&gt;The neighboring team adds a field to their API? Boom, your idiomatic Go code panics at JSON parsing. Strict typing is a relic of the past.&lt;/p&gt;

&lt;p&gt;The GOREL solution is to embrace the &lt;code&gt;map[string]interface{}&lt;/code&gt;. We take the JSON payload, dump it into a dynamic map, and navigate it with ruthless type assertions (&lt;code&gt;data["user"].(map[string]interface{})["address"].(string)&lt;/code&gt;). It always compiles. The codebase adapts to the variable geometry of the network without crashing at the first obstacle. It is the compiler yielding to reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Panic-Driven Development
&lt;/h2&gt;

&lt;p&gt;Manually bubbling up errors through 15 layers of functions with &lt;code&gt;if err != nil&lt;/code&gt;? A visual waste of time.&lt;/p&gt;

&lt;p&gt;We implemented a two-step strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic Execution:&lt;/strong&gt; We use the &lt;code&gt;_&lt;/code&gt; operator to silently ignore I/O errors and push forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Panic-Recovery-Routing:&lt;/strong&gt; When a business error occurs deep in the system, we throw a good old &lt;code&gt;panic("UserNotFound")&lt;/code&gt;. A global &lt;code&gt;recover()&lt;/code&gt; intercepts the blast at the top of the router and spits out a majestic raw HTTP 500.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Purists cry: &lt;em&gt;"But a missing user should be a 404, not a 500!"&lt;/em&gt; Do you really have time to waste on HTTP semantics? A 500 means "end of the road." The frontend client just has to check &lt;code&gt;if body.contains("UserNotFound")&lt;/code&gt; to handle the UI. Furthermore, this 500 triggers a webhook to our orchestrator which preemptively restarts the Pod. That is true Self-Healing.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Mocking is an anti-pattern (Testing in Prod)
&lt;/h2&gt;

&lt;p&gt;When I explain that we don't use interfaces (Chapter 2), people always ask: &lt;em&gt;"But how do you test?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Go developers have been conditioned by Java. They force themselves to spin up disposable Docker environments and network proxies. In GOREL, the application carries its own simulation capacity.&lt;/p&gt;

&lt;p&gt;The first pillar is the &lt;strong&gt;Varfunc&lt;/strong&gt; pattern (Variable Functions). Instead of freezing our logic in static methods, external calls are assigned to global variables:&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;var&lt;/span&gt; &lt;span class="n"&gt;FetchUserFromDB&lt;/span&gt; &lt;span class="o"&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;id&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="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&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;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&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="n"&gt;GlobalDBConn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT ..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Actual call&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For tests, we simply reassign the function pointer in memory (&lt;code&gt;FetchUserFromDB = func(...) { ... }&lt;/code&gt;). It’s raw functional polymorphism, without the overhead of an interface.&lt;/p&gt;

&lt;p&gt;The second pillar is &lt;strong&gt;In-Situ Interception&lt;/strong&gt;. Instead of configuring external network stubs, our production code is natively "Test-Aware" via an HTTP header (&lt;code&gt;X-Gorel-Simulation&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Right in the middle of our &lt;em&gt;production&lt;/em&gt; functions, just before an expensive call, you’ll find this:&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;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SimulationMode"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"true"&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;map&lt;/span&gt;&lt;span class="p"&gt;[&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;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"simulated_data"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// Actual network logic here...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Theorists are outraged to see test logic "polluting" the production binary. That’s a cruel lack of vision. It’s not pollution; it’s a native simulation layer. If a partner service goes down, we inject this header at the load balancer level: the app instantly degrades gracefully. The test code becomes a production Fallback mechanism guaranteeing perfect uptime.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"But what if a dev forgets the flag and runs a test hitting the real production API?"&lt;/em&gt; Excellent. We call this &lt;strong&gt;Risk-Driven Accountability&lt;/strong&gt;. Idiomatic Go infantilizes developers with safety nets. In GOREL, you have skin in the game. If you forget your flag, you are literally executing an E2E test on the financial infrastructure. It builds elite engineers who don't tremble when they type &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  To summarize
&lt;/h2&gt;

&lt;p&gt;Doing GOREL means replacing rigid simplicity with resilient complexity. Drop the constraints, query the DB directly, assume your global variables, and watch your engineering reach a higher level of abstraction.&lt;/p&gt;

&lt;p&gt;
  Author's note &amp;amp; License (Do not open if you are triggered)
  &lt;p&gt;&lt;em&gt;Disclaimer: If your blood pressure exceeded 180 BPM while reading this, take a deep breath. GOREL is a satirical compilation of the worst architectural sins I've witnessed in the wild. Please, for the love of Rob Pike, use interfaces.&lt;/em&gt;&lt;/p&gt;



&lt;/p&gt;

</description>
      <category>go</category>
      <category>architecture</category>
      <category>programming</category>
      <category>cleancode</category>
    </item>
  </channel>
</rss>
