Modern Software Architecture (Part 1): Boundaries, Trade-Offs, and Coordination
A practical guide to understanding cohesion, coupling, architectural characteristics, bounded contexts, connascence, and the real coordination costs behind modern distributed systems.
There is a specific kind of meeting that happens at every company once it reaches a certain scale. The engineering team is gathered, the slides are prepared, and someone from the platform team is about to explain why the system that worked perfectly fine six months ago is now responsible for most production incidents, the slowest deploys, and the longest on-call rotations. The word “microservices” will appear in the presentation. So will “event-driven.” The argument will be technically coherent and organizationally naive, and the migration it proposes will cost eighteen months and leave the system in a shape that is worse in different ways than the shape it was in before.
I have been in that meeting. I have run that meeting. And years later, I have sat across the table from engineers who survived the migration I proposed and watched them explain, patiently, what we got wrong.
This is an article about architecture. Not the whiteboard kind where you draw boxes and arrows until the diagram looks sufficiently serious. The real kind, where every decision trades one class of problem for another, where the organization shapes the system whether you acknowledge it or not, and where AI-assisted development has dramatically accelerated how quickly teams accumulate complexity without changing the underlying physics of distributed systems.
The Illusion of Technology-First Architecture
When North Harbor Realty, a mid-sized real estate brokerage operating across Chicago and the surrounding suburbs, started building its internal platform, the initial architecture looked like most initial architectures: a monolithic Rails application backed by a PostgreSQL database, a couple of background job queues, and an S3 bucket full of listing images. It worked. It handled MLS ingestion from the three primary providers they were connected to, stored canonical listing data, surfaced agent profiles, and powered the search page that most of their buyer traffic hit every morning between eight and eleven.
The problems started when the business began growing in directions the original architecture had never anticipated. They onboarded two additional MLS providers, each with its own data format, feed schedule, and interpretation of what constituted a unique listing. They added an image processing pipeline after the product team became convinced that automated quality scoring and virtual staging previews would improve buyer conversion. They contracted with an AI recommendation vendor whose models required near-realtime listing data delivered in a very specific schema. They hired thirty new agents in eighteen months, which put pressure on the lead routing and notification systems in ways nobody had modeled.
The response to that complexity was predictable. Someone drew a services diagram. The diagram showed eight boxes. Each box was a microservice. The argument was that by decomposing the monolith along functional lines, each team could deploy independently, scale independently, and evolve its part of the system without coordinating every release across the organization.
The argument was not wrong. But it was incomplete in the ways that matter most.
The engineers who built that migration learned something you can only really learn by building it: technology does not determine architecture. Business behavior determines architecture. The question is rarely whether something should become a service or remain a module. The real question is what coordination costs each option introduces, and whether the organization is actually capable of paying for them.
Architecture as Trade-Off Management
Architecture is the management of irreversible trade-offs.
Neal Ford has a formulation I return to often. Architecture is not the search for the best solution. It is the search for the least worst solution. Every architectural decision trades one failure mode for another, one form of complexity for another, one kind of operational pain for another kind. The job is to understand which pain you can manage and which pain will kill you.
At North Harbor, the listing deduplication problem made this concrete. When a property is listed by multiple agents through multiple MLS providers, you end up with multiple listing records representing the same physical property. These records have different IDs, slightly different addresses, different photo sets, and different pricing histories. Deduplication is expensive. It requires fuzzy matching on address normalization, geospatial proximity checks, and machine-learned similarity scoring across listing attributes. The question was where this computation should live.
The technology-first answer would have produced a deduplication microservice with its own database, deploy cycle, and ownership boundary. The architectural answer required asking a different set of questions. What does the deduplication result affect? It affects the canonical listing record, which is the source of truth for search indexing, AI recommendations, agent commissions, and the legal history of what was listed and when. What happens when deduplication is wrong? Buyers see duplicate listings. Agents receive incorrect commission calculations. MLS reconciliation jobs fail. Some of those failures are operationally annoying. Others are legally dangerous.
Given those consequences, the consistency requirements for deduplication were extremely high. You could not tolerate a state where the canonical listing system and the deduplication system had diverged. Eventual consistency is a perfectly reasonable trade-off in systems where temporary divergence is acceptable. It was not acceptable here.
The decision was to keep deduplication logic inside the canonical listing module rather than extracting it into a separate service. This was not a failure of vision or a lack of ambition. It was an accurate reading of what the system actually needed. Deduplication needed to participate in the same transaction as the canonical listing write. Making that work across a network boundary would have required distributed transactions, which are expensive to implement correctly and notoriously difficult to operate. The coordination cost of the “correct” microservice architecture was higher than the system could afford.
This is what architecture as trade-off management looks like in practice. Not philosophy. Not diagram aesthetics. Cost accounting applied to system design.
Cohesion and Coupling Beyond Textbook Definitions
Every architecture course covers cohesion and coupling. The textbook definition is simple:
high cohesion means things that change together stay together, while low coupling means things that do not need to change together are separated.
The definition is correct and almost useless in production because it does not tell you how to recognize these properties operationally or what to do when they conflict.
The operational version of cohesion looks different. Code is cohesive when the things inside it share the same reason to change, the same deployment cadence, the same consistency requirements, and usually the same operational pressures.
If one part of a module changes every time an MLS provider modifies its feed format, while another changes every time the product team redesigns how listings appear on the website, those pieces of code may live together physically while having almost nothing in common operationally.
At North Harbor, the provider normalization layer and the canonical listing store initially lived in the same codebase and shared the same deployment pipeline. When the normalization logic needed an urgent fix because one of the MLS providers changed its latitude and longitude encoding format, the deploy also touched the canonical listing system, which triggered the full regression suite. The regression suite took forty minutes. That meant a forty-minute delay on a fix affecting listing accuracy for the live Chicago market during peak morning traffic. That pain was a cohesion signal.
The provider normalization code had a completely different deployment urgency profile than the canonical listing code. It changed more frequently, for different reasons, driven by external provider behavior rather than internal product decisions. Separating those concerns was not originally a microservice decision. It was a cohesion decision expressed first as a module boundary and eventually as its own deployable component once the operational cost of shared deployment became impossible to ignore.
Coupling is the inverse problem. The textbook says low coupling is good. In practice, every non-trivial system is coupled in some direction, and the architectural question is not whether coupling exists but where it exists and what form it takes.
Some forms of coupling are manageable. Passing structured data between components through a stable contract is usually safe. Other forms are far more expensive. Temporal coupling, where one system must be available for another system to complete its work, turns small failures into cascading outages. Deployment coupling is even worse because teams often do not notice it until a harmless-looking release breaks production.
The notification system at North Harbor carried invisible deployment coupling for most of its early life. Notifications were triggered by events in the listing lifecycle: new listings matching a buyer’s saved search, price reductions on watched properties, and status transitions from active to under contract. These events were defined as constants inside the listing module and consumed directly by the notification system.
When the product team introduced a new listing status called “coming soon” and changed how status transitions were modeled internally, the notification system broke because it was coupled to the internal representation of listing status rather than to a stable contract.
This is where connascence becomes a more useful concept than coupling. The real problem was not simply that the systems depended on each other. The problem was that they depended on the same meaning, the same internal representation, and the same assumptions evolving in lockstep across deployment boundaries.
Connascence and the Cost of Coordination
Connascence is a term from Meilir Page-Jones that most engineers encounter once and promptly forget. It deserves more attention because it gives you a more precise language for describing the kinds of coupling that create the most operational pain.
Connascence describes situations where two parts of a system must evolve together to remain correct. The important distinction is not whether connascence exists. Every non-trivial system has it somewhere. The real question is what kind of connascence you are creating and how expensive it becomes once it crosses team, deployment, or network boundaries.
Some forms are relatively harmless. Agreeing on a field name is manageable. Sharing the meaning of internal state transitions is far more dangerous. The most expensive form is usually temporal connascence, where one system must execute within a particular order or time window for another system to function correctly.
The notification system problem at North Harbor was a form of connascence of meaning. The notification code and the listing code shared an interpretation of what listing statuses meant and how those statuses transitioned. When that meaning changed, both components needed to change together, but nothing in the system explicitly enforced this requirement. It was an implicit contract made visible only when it broke.
The fix was not simply to rearrange code. The fix was to replace connascence of meaning with a weaker form of connascence by introducing a stable event contract. The listing module now publishes ListingStatusChanged events using a documented and versioned schema. The notification system consumes those events without knowing anything about the internal representation of listing status. The listing system is free to evolve its internal model as long as the published contract remains stable.
This is the real architectural value of event-driven patterns. Not that they are inherently more scalable or more modern. It is that they allow you to convert strong forms of connascence into weaker forms by placing an explicit contract at the boundary. The contract becomes visible, versioned, testable, and operationally observable. It turns an implicit coordination requirement into an explicit one, which is almost always an improvement.
The image processing pipeline exposed a different problem: connascence of timing. Image processing at North Harbor is a genuinely heavy workload. When a new listing is ingested, there may be thirty to fifty raw images from the agent, each requiring resizing into multiple formats, quality scoring, content policy checks, and processing through a virtual staging model that digitally furnishes empty rooms. On a good day, the complete pipeline for a large listing takes eight minutes. On a bad day, when the GPU cluster is already saturated by recommendation workloads, it can take forty.
The original architecture coupled image processing synchronously to the listing ingestion flow. When an agent submitted a listing, the system processed the images before returning a response. The result was a dangerous form of temporal connascence between listing ingestion and image processing capacity. If image processing slowed down, listing ingestion slowed down. If the GPU cluster failed, listing ingestion failed. Two unrelated operational concerns had effectively been welded together by a timing dependency.
Decoupling the image pipeline into an asynchronous queue was not particularly interesting from a technical perspective. Message queues are not novel technology. What mattered was acknowledging why the synchronous coupling existed in the first place. The product team wanted listings to appear in search results with images immediately after publication.
The asynchronous design forced the organization to confront the trade-off honestly. Listings would sometimes appear in search with a “photos processing” indicator for up to forty minutes, and ranking quality would temporarily degrade because image quality scores were not yet available. This was not just a technical compromise. It was a product decision about what kind of operational dependency the business was willing to tolerate.
The architecture only moved forward once the coordination cost of synchronous coupling was explained in business terms rather than technical ones.
Architectural Characteristics and Business Pressure
There is a framework from Mark Richards and Neal Ford in Fundamentals of Software Architecture that I have found more useful than almost any architecture diagram I have seen in a review meeting: architectural characteristics. These are the non-functional properties a system must exhibit in order to satisfy the business. They are specific enough to measure, concrete enough to shape architecture, and operational enough to expose trade-offs that diagrams usually hide.
At North Harbor, the characteristics became obvious once you stopped looking at the system as a collection of features and started looking at it as a collection of business pressures.
The canonical listing store needed consistency above almost everything else. A stale listing, a duplicate listing, or an incorrect status was not simply a user experience problem. Agents had legal obligations around listing accuracy. Buyers making offers based on incorrect data created liability for the brokerage. The consistency requirement was a business and legal requirement first, and only secondarily a technical one.
The search infrastructure had a completely different set of pressures. It needed availability and low latency. The Chicago residential market has strong daily traffic patterns. Search traffic spikes sharply between eight and eleven in the morning, dips through the afternoon, and rises again between six and nine in the evening when people browse listings from home. A/B testing showed that search latency above three hundred milliseconds produced measurable conversion decline.
At the same time, buyers still expected search to work even when the canonical listing system was running large reconciliation jobs or undergoing maintenance. Nobody browsing condos in Lincoln Park at 8:30 in the morning cared that an internal consistency process was saturating the primary database.
The lead routing system had yet another profile. It needed throughput and resilience. When a buyer submits an inquiry, the brokerage has a narrow window to match that lead to the right agent before the buyer loses interest or contacts a competing brokerage. The routing logic itself is complicated. It incorporates geographic assignment, agent specialization, contractual lead distribution agreements, and load balancing rules designed to prevent top-performing agents from becoming overloaded.
Putting all of that logic directly in a latency-sensitive execution path meant that small slowdowns immediately became lost leads.
These differences are the real reason service boundaries emerged at North Harbor. Not because the organization wanted microservices as a statement of technical maturity, but because the systems were being pulled toward incompatible operational requirements.
The canonical listing system optimized for correctness and consistency. The search infrastructure optimized for latency and availability. The lead routing pipeline optimized for throughput and resilience.
Running those concerns inside the same deployment would have forced damaging compromises. Consistency requirements would push toward locking strategies that harmed search latency. Search availability requirements would encourage aggressive caching strategies that undermined canonical correctness. Lead routing throughput requirements would force time-sensitive assignment workloads to compete for the same infrastructure resources as reconciliation jobs and listing updates.
Characteristics-driven architecture is different from function-driven architecture. You are not decomposing systems only according to what they do. You are decomposing systems according to the operational realities they must survive.
Strategic DDD and Bounded Contexts
Domain-Driven Design has accumulated enough cargo cult adoption over the years that it is worth being precise about which parts are genuinely useful and which parts mostly exist to make architecture workshops sound sophisticated.
The useful part is the concept of bounded contexts and the linguistic discipline they impose. A bounded context is a boundary within which a model remains internally consistent and complete. Inside that boundary, words mean what they mean without ambiguity. Outside the boundary, the same word may legitimately mean something different, and that difference is not academic. It has operational consequences. At North Harbor, the word “listing” was the clearest example.
Inside the MLS ingestion context, a listing was a raw provider feed record with a provider ID, ingestion metadata, and a set of fields that varied depending on which MLS system produced it. Inside the canonical listing context, a listing was something very different: a normalized, de-duplicated representation of a property with a North Harbor identifier, a confidence score, and a lifecycle state representing the brokerage’s best understanding of the property’s actual market status.
Inside the search context, a listing was not really a transactional entity at all. It was a retrieval document optimized for speed. It contained denormalized agent information, precomputed geographic clustering, search-oriented text fields, and ranking metadata designed to support low-latency queries during peak traffic hours.
Inside the commission accounting context, a listing became a financial object tied to transaction prices, commission splits, contractual obligations, and payout calculations. These were not four views of the same model. They were four genuinely different models of the same real-world concept.
Trying to force all of them into a single enterprise-wide “Listing” model would have created one of two outcomes. Either the model would become so abstract that it would serve none of the contexts particularly well, or it would become so overloaded with competing concerns that changes required by one part of the organization would constantly destabilize the others.
The bounded context discipline rejects both options. Each context gets its own model, its own language, and often its own storage model optimized for its operational needs. Translation happens explicitly at the boundary in code that is visible, testable, and operationally observable.
At North Harbor, that translation was handled through anti-corruption layers between the MLS ingestion context and the canonical listing context. Provider feed records were transformed into canonical listing representations through explicit mapping rules. Provider-specific fields were normalized, ambiguities were resolved according to documented business rules, and every transformation decision was logged for auditing and reconciliation purposes.
This mattered because providers routinely changed their schemas without notice. During the first year alone, multiple ingestion failures were traced back to silent feed changes from MLS vendors. The anti-corruption layer isolated those failures to the translation boundary rather than allowing corrupted provider data to leak directly into the canonical store.
Event storming, the workshop technique created by Alberto Brandolini, also turned out to be genuinely useful at North Harbor, though not for the reasons architecture diagrams usually suggest.
The value was not the sticky notes. The value was visibility. Putting domain events on a wall and asking “what caused this event?” and “what reacts to this event?” exposed workflow boundaries that the original technology-first decomposition never revealed. It became obvious, for example, that listing lifecycle events formed the backbone of almost every major workflow in the organization.
ListingCreatedListingActivatedListingModifiedListingUnderContractListingSoldListingExpired
Search indexing reacted to them. Notifications reacted to them. Commission calculations depended on them. Agent performance reporting derived metrics from them. Reconciliation jobs used them to verify listing state transitions across providers.
The listing lifecycle was not merely a database field. It was a first-class architectural concept around which multiple operational workflows coordinated themselves.
That distinction mattered because it changed how the system boundaries were designed. The architecture stopped being organized around database tables and started being organized around business behavior and coordination patterns.
Architectural Quanta and Granularity
The architectural quantum, a term Mark Richards and Neal Ford use to describe the smallest deployable unit with high functional cohesion and all the structural pieces required to operate independently, is one of the more useful ways to think about architectural granularity.
The important question is not whether a system should use microservices or remain a monolith. The important question is what level of granularity each part of the system actually requires given its operational characteristics, change frequency, ownership structure, and coordination cost. At North Harbor, different parts of the platform evolved toward different granularities for entirely different reasons.
The canonical listing store, commission accounting system, and agent profile management system remained modules inside a shared runtime for the first three years of operation. They shared a database, deployed together, and were maintained by heavily overlapping teams. Their change rates were similar, their consistency requirements were compatible, and separating them would have introduced more operational coordination than the organization would have gained in flexibility.
Keeping them together was not architectural laziness. It was an economically rational decision. The image processing pipeline evolved in the opposite direction almost immediately. Its operational profile had very little in common with the rest of the platform. It required GPU access, consumed memory aggressively, and experienced bursty workloads whenever large listing imports arrived from MLS providers. Its failure modes were also isolated from the transactional concerns of the canonical listing system. A runaway image-processing workload should never be capable of starving listing writes or exhausting memory inside the systems responsible for maintaining canonical market data.
The separation happened because the operational realities diverged, not because the organization suddenly became enthusiastic about microservices. The search infrastructure followed a similar path. As traffic increased, North Harbor eventually moved semantic property search into its own runtime backed by a dedicated Elasticsearch cluster optimized for retrieval latency rather than transactional consistency.
The search system had requirements the canonical listing store simply did not share. It needed denormalized retrieval documents, vector similarity search for buyer preference matching, aggressive caching strategies, and infrastructure optimized for read-heavy workloads during peak browsing hours. Its scaling model, backup strategy, operational tooling, and performance tuning profile gradually became independent enough that keeping it inside the same runtime stopped making operational sense.
The lead routing system split for a different reason entirely: latency sensitivity.
At one point, lead assignment and outbound notifications shared the same runtime and worker infrastructure. Most of the time this was fine. Occasionally it was disastrous.
Notification delivery workloads were unpredictable. Push notification providers slowed down. SMS gateways rate-limited traffic. Email retries piled up during external outages. None of those problems were existential because delayed notifications were usually tolerable. A buyer receiving a saved-search alert ten seconds late was not catastrophic. A lead assignment arriving ten seconds late was.
The brokerage operated in a market where response speed directly affected conversion rates. Buyers contacting agents through the platform were often simultaneously browsing competing brokerages. Delays in routing a lead to the appropriate agent translated directly into lost opportunities.
Eventually the team realized they had accidentally coupled a latency-sensitive workflow to a best-effort communication system with entirely different operational expectations. That realization, more than any architecture diagram, justified the runtime split.
This is what granularity decisions look like in practice. Systems separate because their operational realities diverge enough that shared coordination becomes more expensive than independent operation. Not because someone drew more boxes on a diagram.
Monoliths, Distributed Systems, and the Real Cost
There is an enormous amount of content on the internet about monoliths versus microservices. Most of it is not especially useful because it argues from aesthetics rather than cost. Distributed systems are expensive. This is not an opinion or a stylistic preference. It is a structural property of distributed computing itself.
The network is not reliable. Latency is not zero. Bandwidth is not infinite. Clocks are not perfectly synchronized. Topology changes. Failures are partial rather than obvious. Every distributed system inherits these constraints whether the architecture diagrams acknowledge them or not. At North Harbor, the reconciliation workflow exposed those costs very clearly.
The reconciliation process runs twice per day. Its responsibility is to compare the canonical listing state against the latest MLS provider feeds and resolve discrepancies between them. While the system operated as a monolith, reconciliation was mostly a transactional database operation. The consistency boundary was local. Failure handling was relatively straightforward. Once the system became distributed, reconciliation stopped being a transaction and became a coordination protocol.
Now the process involved multiple services with different consistency guarantees, different availability profiles, and independent deployment schedules. Updates could arrive out of order. One service could process a listing modification before another service had even received the event describing it. Temporary divergence became normal rather than exceptional.
Compensating workflows had to be introduced for situations where one subsystem accepted a change while another had not yet observed it. Every write path required idempotency guarantees because retries were no longer theoretical edge cases. Distributed locking was added to prevent concurrent reconciliation runs from producing contradictory canonical states. None of this logic was impossible to implement. All of it was expensive to operate.
The engineers who built the system were competent. The code was careful. The architecture reviews were thorough. The system still produced incorrect reconciliation results three times during the first six months after the migration. The failures were subtle.
Diagnosing them required correlating logs across multiple services, reconstructing event ordering from clocks that were not perfectly synchronized, and reasoning about race conditions that appeared naturally in production traffic but almost never reproduced cleanly in test environments.
The monolith would have handled all three incidents with a single database rollback.
This is not an argument against distributed systems. North Harbor’s image processing pipeline genuinely needed runtime isolation. The search infrastructure genuinely required dedicated resources and an independent scaling model. Those were legitimate operational pressures.
The problem begins when distribution becomes the default assumption rather than a response to concrete requirements. Distributed systems are not evidence of architectural maturity. They are a trade-off that exchanges local complexity for coordination complexity, operational overhead, and failure modes that are significantly harder to reason about.
Serious engineers do not build distributed systems because distributed systems are fashionable. They build them when a single runtime can no longer satisfy the operational realities of the business. If those realities can still be handled by a well-structured modular monolith, then a well-structured modular monolith is not a compromise. It is the correct architecture.
Conway’s Law and Organizational Architecture
Conway’s Law states that organizations tend to produce systems that mirror their communication structures. This is not a metaphor or a management slogan. It is a recurring operational reality.
At North Harbor, the organization changed faster than the architecture for almost two years. The engineering team grew from three engineers to roughly thirty in less than eighteen months. Teams formed, split apart, merged, and reorganized repeatedly as business priorities shifted quarter by quarter.
The architecture did not evolve at the same speed. As a result, module boundaries created during the three-engineer phase quietly became ownership boundaries in the thirty-engineer phase, despite never having been designed for that purpose.
The canonical listing system exposed the problem clearly. Officially, the listings team owned it. In practice, almost every team modified it. The search team needed additional indexing fields. The recommendations team wanted richer buyer preference metadata. The integrations team needed provider-specific flags. The agent tools team needed additional lifecycle states.
Every request was individually reasonable. Collectively, they produced a system with weak ownership boundaries and steadily increasing internal incoherence. The canonical listing model slowly became a dumping ground for cross-team concerns because the organization had never established who was responsible for protecting the integrity of the boundary itself.
The eventual fix was organizational before it was technical. The listings team was given explicit ownership over the canonical schema along with veto authority over structural changes. Other teams could still propose modifications, but the listings team reviewed those requests for consistency and frequently redirected teams toward consuming listing lifecycle events instead of directly extending the canonical model itself.
That governance decision stabilized the architecture more effectively than any refactoring effort had managed to do. The inverse form of Conway’s Law, sometimes called the Inverse Conway Maneuver, is the deliberate design of organizational structures intended to encourage particular architectural outcomes.
North Harbor applied this principle when the company created a dedicated platform engineering team responsible for shared infrastructure concerns: the event streaming platform, deployment tooling, observability systems, and runtime infrastructure used across product teams.
Before that reorganization, those concerns evolved opportunistically inside unrelated application codebases. Logging conventions differed between teams. Deployment tooling drifted. Monitoring quality depended almost entirely on which engineer had last touched the service.
Creating a dedicated platform team changed the coordination structure around those concerns. Shared infrastructure stopped evolving as scattered local optimizations and started evolving as coherent systems with explicit ownership.
This is the part of architecture literature that many engineers underestimate. Architecture is organizational as much as it is technical. You cannot consistently produce clean system boundaries inside an organization whose communication structure continuously undermines them.
AI Changed Software Delivery, Not Architectural Reality
The final point is the most important and probably the most misunderstood.
AI-assisted development tools genuinely change what a small engineering organization can build in a fixed amount of time. Code completion systems, context-aware review tooling, and autonomous coding agents compress implementation time dramatically.
At North Harbor, teams using strong AI-assisted workflows routinely shipped feature work that would previously have required significantly larger engineering teams. Feedback loops became shorter. Boilerplate disappeared faster. Moving from written requirements to working code became materially easier. None of this changed the architectural fundamentals.
AI systems accelerate code generation. They do not reliably generate sound architectural judgment. An AI model cannot determine whether deduplication logic belongs inside the same transactional boundary as the canonical listing store because that decision depends on business liability, operational tolerances, organizational ownership, reconciliation behavior, and the real-world consequences of inconsistency in a real estate market.
Those are architectural decisions rooted in context rather than syntax. What AI changes most aggressively is the speed at which organizations accumulate complexity.
A team capable of shipping three times more functionality per quarter is also capable of accumulating three times more architectural debt if its structural decisions are weak. Faster delivery amplifies both good architecture and bad architecture simultaneously.
The engineers at North Harbor eventually identified a failure mode that became dramatically more common once AI-assisted coding workflows entered the organization: coherent local code paired with incoherent global structure.
An AI tool can generate a perfectly functioning feature that quietly violates a bounded context boundary, introduces hidden connascence between systems, or leaks internal semantics across service contracts. The generated code may be completely correct in isolation while still damaging the long-term coherence of the system.
The faster code generation becomes, the more valuable architectural awareness becomes. Architecture in the AI era is not less important than it was before. It is more important because the rate of change is higher, the surface area of systems grows faster, and the consequences of weak structural decisions compound more quickly.
The discipline of drawing boundaries carefully, managing coordination costs deliberately, and understanding what a system actually needs to optimize for has never mattered more. The engineers who have operated North Harbor’s platform through multiple years of growth eventually reached the same conclusion shared by nearly every organization that survives its own scale:
The architectural decisions that age well are rarely the ones chasing the newest technology or the most fashionable patterns. They are the decisions that correctly understand the problem, honestly assess the operational cost, and choose trade-offs the organization can realistically sustain. That is still what software architecture is. It was true when the stack was Rails and PostgreSQL.
It remains true when the stack includes vector databases, semantic search, embedding pipelines, and AI-assisted development systems. The tools evolve. The physics do not.
This is Part One of an ongoing series on software architecture for production systems. Part Two will explore architectural fitness functions, evolutionary architecture, and how system boundaries drift under organizational pressure over time.



Great work! You have so much valuable experience, you should teach others. One small request: could you please provide the next article with more subheadings and bullet points, and also highlight principles visually differently than practical examples? My old eyes find their way around better that way ;-) Thank you very much!