<?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: Adrián Hernández</title>
    <description>The latest articles on DEV Community by Adrián Hernández (@adrian2monk).</description>
    <link>https://dev.to/adrian2monk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F63953%2Fb4d37c7b-85a8-41bc-af14-a8ec8c0fa5b2.jpg</url>
      <title>DEV Community: Adrián Hernández</title>
      <link>https://dev.to/adrian2monk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adrian2monk"/>
    <language>en</language>
    <item>
      <title>Inversion of Control (IoC)</title>
      <dc:creator>Adrián Hernández</dc:creator>
      <pubDate>Wed, 01 Jul 2026 06:00:50 +0000</pubDate>
      <link>https://dev.to/adrian2monk/inversion-of-control-ioc-2a90</link>
      <guid>https://dev.to/adrian2monk/inversion-of-control-ioc-2a90</guid>
      <description>&lt;p&gt;I know that many of you are familiar with this subject. But what I'm going to say isn't about the right description of this pattern; I'm just extrapolating the name for another usage. Don't get me wrong, they are essentially the same concept, but as a principle, it has too much value way above the function definition, actually.&lt;/p&gt;

&lt;p&gt;If a function does too many things, it may need dependencies to do them. What you usually do, when it applies, and that's always an “it depends" engineering thing, is give the control to the dependency instead of keeping it in the parent. That often works. I wanted to look at an example here, and since I come from the Node.js world, I can say that Express.js is the most dead-simple example of it. (I had to bake first because honestly, I wasn't sure at first that that was what Express middleware is) But I must say I stumbled on this idea again while reviewing an HTTP middleware chain in my current Golang project. In a nutshell, your code doesn't control the app lifecycle; the Express library does that for you, but what Express does is give you control over what to do with every request. This is the textbook composition you need a dependency to run your code (an http lifecycle engine) but instead of importing it and using it to handle your logic you compact your logic within a function and pass it to the library: “do not call us; we call you” the Hollywood principle they say (I found that sticky phrase while I debated my ideas with an LLM today).&lt;/p&gt;

&lt;p&gt;So here is the new problem/concept about IoC that I want to share today: it's not always trivially found in function composition and arguments, but sometimes in architecture as well. You may have a fat method or class that does too many things, with some of that work handled by its components. It doesn't mean this is necessarily wrong. In fact, I prefer most deep modules to shallow ones, per John Ousterhout (A Philosophy of Software Design). But then I realize that when I see the pattern start to tumble down because some of the previous invariants no longer hold, the usual way to solve is to look at things from the outside in. It may work for you as well as it has worked for me the last couple of years.&lt;/p&gt;

&lt;p&gt;I started digging into this just recently, when I realized how often I used to reflect on things that seemed wrong or smelled bad in code. So you may want to try once or twice and see if it works.&lt;/p&gt;

&lt;p&gt;A concrete example: I got a basic auth interceptor that handles parsing the auth credentials regardless of the input channel and validates their authenticity using the appropriate method. Let's say we have three of them: cookie-based authentication, Bearer tokens, and full mTLS. Each one of them needs a different workflow and auth validation. I know this isn't the usual way to see it, because you usually get each one separately. But not when you have a distributed policy engine that handles every policy application point across the org. So you not only validate each credential but also ensure that the authenticated actor has the right permissions over each resource it will touch, and manage this centrally. When you describe it like that, it sounds easy to see the right way to put it, but that's not often the case when you start with a single client and then evolve into a more complex consumer architecture. You build on the march under tight deadlines and with a fear of breaking things, so you look for the minimal, easy thing to do and build on what is already there. That's nobody's fault or everybody's responsibility. There is no single developer in charge of that piece of code, but a team effort to keep things clean and legible. Sometimes not aligned with the product, certainly, and probably it requires a little bit of culture and self-awareness to keep it just like that. There is no way to keep it simple or a house clean without energy waste. That's the second law of thermodynamics, also known as the law of entropy. Even if you don't move or change anything, things around you that you or your app can't control change.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
&lt;strong&gt;The Fix&lt;/strong&gt;&lt;br&gt;
Gives the policy engine as a dependency of every input middleware, rather than having a single interceptor with three dependencies. Especially when you are sure that every piece of this is exclusive and independent, and that makes the reasoning even simpler. You don't need a bearer interceptor when validating cookies, and vice versa. mTLS may not need any of the above, so what's the point of building something that injects optional dependencies one at a time when we won't need all three in every case?&lt;br&gt;

&lt;/div&gt;


&lt;p&gt;So, looking for ways to simplify code is always on my mind. This is just one of them: invert control, which gives your code a dependency on its dependencies when it becomes too bloated or hard to maintain. Do the opposite if you have too many dependencies and want to simplify and consolidate logic in one place. This is always the first way I start. Consolidate first, then diversify; monolith first, then microservices; orchestrate first, then choreography. Start simple and explicit, then become abstract and decouple. But that's just my vision, and you may have a different strategy to approach this yourself. &lt;/p&gt;

&lt;p&gt;Have you ever encountered a 'fat' interceptor that tried to do too much? How did you decouple it? I’d love to hear your strategies in the comments!&lt;/p&gt;

</description>
      <category>design</category>
      <category>patterns</category>
      <category>architecture</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Data-Driven Architecture</title>
      <dc:creator>Adrián Hernández</dc:creator>
      <pubDate>Mon, 30 Mar 2026 22:00:48 +0000</pubDate>
      <link>https://dev.to/adrian2monk/data-driven-architecture-5gph</link>
      <guid>https://dev.to/adrian2monk/data-driven-architecture-5gph</guid>
      <description>&lt;h3&gt;
  
  
  The layering problem
&lt;/h3&gt;

&lt;p&gt;Applications often have many layers, such as repositories and ORMs, due to patterns like MVVM, MVC, and the Hexagonal architecture. My main argument is that these architectural decisions, while intended for general use, often feel rigid or contextless, especially for microservices or even monorepos. The art of architecture is about composing components into systems, but this process relies heavily on context and appreciation. Patterns exist as recipes for recurring problems, but when applied without context, they can feel forced and less relevant. Examples from books rarely match your real problem, and frameworks can lock you into abstractions that don’t match your needs.&lt;/p&gt;

&lt;p&gt;Here’s my thought: let problems find you first. If they don’t, it’s either because they don’t belong or you don’t recognize them. Reading about them isn’t enough. It’s better to try building with these patterns elsewhere—not at work—unless guided by someone more experienced. Sometimes practice is best, or even necessary, when speed matters. Frameworks exist because they embed valid patterns for specific cases. Companies offer ready-made solutions like ORMs, ERPs, or Next.js; you may use them if patterns fit. Otherwise, question and resist the urge to apply patterns by default. Avoid Resume-Driven-Development traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data-Driven Design
&lt;/h3&gt;

&lt;p&gt;While there is literature about high-level architectural design and modules in “The Philosophy of Software Design” that I find interesting, the simplest and most effective driver I’ve found is data itself. My argument leads to a key idea: everything points toward a data-driven approach. Rather than starting with high-level abstractions, focus on your data. Shape your system by following your data and encoding it using clear, simple structures. This approach will usually simplify both design and tool selection, letting your data's characteristics guide architecture and modeling choices to best suit your use case.&lt;/p&gt;

&lt;p&gt;This is DDD at its core, and it also favors deep modules, as the referenced book says. Large modules should fade or be autogenerated. Focus on your domain and API simplicity, which depend on how well you encode data and its relationships. Here, DDD’s data aggregates help: when you need several objects to interact, think of them together as a unit, not just related items. This encourages simple design and cuts down race conditions, as changes to coupled parts remain atomic. Other concepts exist—better explained elsewhere—but everything comes down to business, data, or algorithm invariants. Find these invariants, then encode them. Use an automaton. If you could learn one thing in Computing Science, pick a DFA. It’s a first principle to help avoid over-engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automata and the Power of Explicit State
&lt;/h3&gt;

&lt;p&gt;Finite Automata excel because they work with data and can process almost any computable input. Bugs often stem from accepting too much or omitting needed data. Protocols frequently use this concept. Though RFCs and implementations differ, starting with a DFA is wise for such problems. It’s the simplest way to define data boundaries and is nearly linear in speed. If your issue is data-related, always think it through with automation first. This clarifies your approach and shows if another data structure is needed. Don’t jump into polished solutions when starting new work, if you can avoid it.&lt;/p&gt;

&lt;p&gt;Many think you shouldn’t build from scratch, which makes sense given today’s abundance of libraries. But if data is core to your business, treat it as seriously as outsourcing. Don’t outsource your core. Even without an MBA, I believe in this, and many smarter people agree. Books like Amazon’s “Working Backwards” or Netflix’s “Engineering Culture” strike me as well-meaning novels. Company realities are messier: every company is a ball of controlled technical chaos to keep up with business scale. To fight this, prioritize your principles as a technical lead and let business leaders handle the rest. Simplify early and check complexity with each new code addition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object-Oriented Pitfalls and the Relational Model Trap
&lt;/h3&gt;

&lt;p&gt;Object-oriented programming’s downside is its link to the relational model. While not always true, big enterprise problems usually stem from data modeling. Academics start from relational algebra, which differs from category theory. Smalltalk’s background is separate from relational algebra, but it’s misapplied to enterprise systems. Originally, code automated processes, not fixed algorithms—this is a flawed outlook. Treat data as a first-class citizen from the start, not an afterthought. While we write in terms of behavior and output, we shouldn’t shape code solely for data or processes. It’s tricky, but embracing this shift leads to simpler code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final recommendations
&lt;/h3&gt;

&lt;p&gt;Below are good starting points for my ideas. I have plenty of reinforcement bias, but I don’t recommend that, or linking to anything that isn’t my interpretation, since people might see things differently. A discussion forum might be better for debate than a blog post. If you’re interested, it’s better to look in books or projects rather than hearing from someone who shares your thoughts—unless it’s just for fun and entertainment, as I do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html" rel="noopener noreferrer"&gt;Screaming Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.destroyallsoftware.com/talks/boundaries" rel="noopener noreferrer"&gt;Boundaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I leave you with some extra advice, from my discussion about the subject with my today’s victim, as lately, ChatGPT or whatever other LLM kind of chat. It turns out that’s the best way to challenge my thoughts nowadays, and it’s worth it for summary or tl;dr sections if you are that kind of guy. But I put it at the end just bc of that. Although I am still looking for perfection on those ideas and principles, if you like, I’m aware that the world is complex enough and you always need to deal with physics one way or another, that means you eventually will face reality and need to talk with external systems and distributed networks, even though you only have a database and OS as a runtime. Because of that, on scale, you will need to think in other layers as well. But that’s the point, if you already have enough layers on top of your system. Why did you shoot yourself in the foot and put more inside your app?&lt;/p&gt;

&lt;h3&gt;
  
  
  The clean mental model (ChatGPT extracted)
&lt;/h3&gt;

&lt;p&gt;Think in three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Domain (DDD core)

&lt;ul&gt;
&lt;li&gt;Strong invariants&lt;/li&gt;
&lt;li&gt;Impossible invalid states&lt;/li&gt;
&lt;li&gt;Explicit transitions&lt;/li&gt;
&lt;li&gt;“What is allowed?”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Application / workflow

&lt;ul&gt;
&lt;li&gt;Ordering of steps&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Orchestration&lt;/li&gt;
&lt;li&gt;“What happens next?”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Infrastructure

&lt;ul&gt;
&lt;li&gt;DBs&lt;/li&gt;
&lt;li&gt;Queues&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most bugs happen when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 2 logic leaks into layer 1&lt;/li&gt;
&lt;li&gt;Invariants are not enforced in layer 1&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Trying to cross-post here since I always wanted to give back to this community what I had learned from it. Other than that, my &lt;a href="https://substack.com/@ahbarrios" rel="noopener noreferrer"&gt;Substack&lt;/a&gt; looks pretty lonely so far. Check this out in case you found this article interesting (it will be published next week). And this may help me with early feedback to include corrections and examples there.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>objects</category>
      <category>layers</category>
    </item>
  </channel>
</rss>
