Capa do artigo: Acyclic Dependencies Principle: from monolith to microservice, from graph theory to the cost of change

Acyclic Dependencies Principle: from monolith to microservice, from graph theory to the cost of change

The complexity that matters in software systems splits into two categories. Essential complexity is what comes from the nature of the problem. It is what the system actually needs to do: business rules, domain constraints, required integrations. It is inherent to the problem being solved and does not go away with shortcuts. Accidental complexity is what arises from how we organize the solution. It comes from choices of structure, technology, how we split modules and how they relate. Unlike essential complexity, it can be reduced with design principles, good architecture and discipline.

The Acyclic Dependencies Principle (ADP) acts on accidental complexity. It requires that the dependency graph between packages or components have no cycles. It does not make the domain simpler, but it prevents the system’s structure from multiplying the cost of maintenance, build and evolution. In this article we will look at how to identify and how to resolve this problem across several dimensions.


Typical scenario and the pain of the cycle

In growing monoliths, the typical scenario is this: module A depends on B, B on C, and C depends back on A.

flowchart LR
  A[Module A] --> B[Module B]
  B --> C[Module C]
  C --> A

When there are dependency cycles, the complexity of change increases drastically. There is no way to compile, test or deploy only part of the system: any change in a module in the cycle forces recompilation and retesting of all of them. Modules end up taking on more responsibilities than they should, because rules and data are spread across many interconnected parts, breaking design principles such as clear separation of responsibilities and class cohesion. Creating automated tests becomes harder, since it is almost impossible to isolate a module without pulling in external dependencies. Moreover, the risk of breaking existing functionality grows, because small changes can trigger chain effects in other modules in the cycle. Circular coupling also undermines code clarity and the predictability of system evolution, making maintenance harder and raising the cost of each change. This cost often stays hidden until someone tries to change a module and discovers they cannot do anything in isolation. The Acyclic Dependencies Principle (ADP) addresses exactly this: by requiring the dependency graph to be acyclic, it restores order and clear boundaries between modules, enables isolated tests, promotes incremental builds and helps keep each component responsible only for what it should.

Nobody plans that cycle. It comes from the combination of several factors: delivery pressure, the absence of an explicit rule that forbids circular dependencies, and recurring local situations. Add to that newcomers joining the team without context of the system design, poorly done technical refinement of increments (when people "fix it quick" without looking at the dependency graph), lack of a defined and communicated application design, absence of architecture documentation or ADRs that guide where each kind of code should live, turnover without handover that preserves this knowledge, and lack of a fitness function (or automated criterion in CI) to validate whether this kind of problem has started to occur. In isolation, each factor may not create the cycle. Together, they make circular coupling almost inevitable over time. Robert C. Martin formulated the principle in the context of package design and the evolution of object-oriented systems. In the book Agile Software Development: Principles, Patterns, and Practices and in articles such as Granularity: The Acyclic Dependencies Principle, he places the ADP alongside the Stable Dependencies Principle (SDP) and the Stable Abstractions Principle (SAP). We will return to these two principles at the end of the article to see how they complement the ADP. Where there is a cycle, there is no order; where the ADP is respected, the structure becomes governable again.


Formal definition and graph theory

In formal terms, the ADP states the following. The dependency graph of packages or components must not contain cycles. In other words, dependencies must form a directed acyclic graph (DAG), i.e. a graph without cycles in which there is a coherent order between nodes. Throughout the article we use package as the main term; module and component appear as synonyms when the context (monolith, service, bounded context) calls for it. Each node represents a package or component. Each directed edge from A to B indicates that A depends on B, i.e. A uses types, functions or resources defined in B. A cycle exists when there is a path that starts at a node and returns to it. In that case there is no way to assign a total order to the modules. There is no "first" or "last", and any change at a vertex in the cycle can force recompilation or retesting of all the others.

"The dependency graph of packages [or components] must not contain cycles." — Robert C. Martin, Granularity: The Acyclic Dependencies Principle

The sentence preserves the author’s original formulation. What characterizes a dependency cycle is the existence of a sequence that closes on itself. If package orders depends on customers, customers on addresses and addresses on orders, the three form a cycle. None of them can be built or understood in isolation. The order of compilation and the order of responsibility become ambiguous.

flowchart LR
  subgraph ciclo["Dependency cycle"]
    P[orders] --> C[customers]
    C --> E[addresses]
    E --> P
  end

By contrast, an acyclic structure allows building first what depends on nothing (leaves of the graph), then what depends only on the leaves, and so on up to the root. This order is the topological ordering (the sequence in which nodes can be processed while respecting dependencies) and it only exists when the graph is acyclic.

flowchart TB
  E[addresses] --> C[customers]
  C --> P[orders]
  style E fill:#e8f5e9
  style C fill:#e8f5e9
  style P fill:#e8f5e9

(Above: cyclic graph. Below: same domain as a DAG, with leaves (addresses) at the top and topological ordering possible.)

In pseudocode, a typical cycle appears when two modules reference each other. The orchestration module imports the rules module, and the rules module, to validate or notify, imports the orchestrator. The conceptual solution is to introduce an abstraction (interface or stable type) that breaks the direct reference, making the dependency unidirectional. The graph no longer has a cycle and topological ordering becomes possible.


Mathematical foundations: DAG, topological ordering and strongly connected components

The foundation of the ADP rests on graph theory. A directed acyclic graph (DAG) is a directed graph without cycles. In a DAG it is always possible to compute a topological ordering, i.e. a linear order of the vertices such that for every edge (u, v), u appears before v. This order is not necessarily unique, but its existence guarantees a sequence of steps consistent with all dependencies. Build systems (Make, Maven, Gradle, Bazel, among others) exploit this property. They model the project as a graph of tasks and run tasks in topological order. If the graph has a cycle, the ordering does not exist and the build fails or becomes undefined.

Strongly connected components (SCCs) are maximal subgraphs in which, for every pair of vertices u and v, there is a path from u to v and from v to u. In short: an SCC is a set of nodes that form one or more cycles among themselves; analysis tools use SCCs to detect cycles. In a DAG, each vertex is a trivial SCC, since there is no cycle. When there are cycles, the vertices that participate in the same cycle belong to the same SCC. The diagram below illustrates a graph with a cycle (A→B→C→A). The three nodes form a single non-trivial SCC. D is a trivial SCC.

flowchart LR
  subgraph scc["SCC (cycle)"]
    A --> B
    B --> C
    C --> A
  end
  D[D]
  A --> D

Static analysis and dependency tools typically detect cycles by identifying SCCs of size greater than 1. Breaking a cycle amounts to removing edges (or nodes) until no non-trivial SCC remains, i.e. until the graph becomes a DAG. Analysis tools do the detection work. The decision of which edge to cut remains human.

The impact on compilation pipelines is direct. In a monolith with cyclic dependencies between packages there is no way to compile only a subset after a change. Any change in a package in the cycle forces recompilation of all of them. In large projects this increases build time, weakens continuous integration and hinders incremental deploy. The ADP, by requiring acyclicity, restores incremental builds (compile and test only what changed) and tests localized by layer or component.


Layered architectures, Clean Architecture, Onion Architecture and Hexagonal (Ports and Adapters) share the idea that dependencies should point in a preferred direction. In general, from what is more volatile or closer to infrastructure toward what is more stable or closer to the domain. The ADP reinforces this by forbidding cycles. If the presentation layer depends on the application layer, that on the domain, and the domain depends on none of them, the graph is acyclic. If at some point the domain comes to depend on infrastructure or the application layer (e.g. to resolve a service or send an event), a cycle or rule violation appears. Dependency inversion (DIP) is the usual technique to restore the correct arrow. The domain defines an interface and the concrete implementation lives in an external package that depends on the domain.

In Clean Architecture the layers are organized in rings. Entities at the center, use cases around them, adapter interfaces and at the edge the implementations (frameworks, UI, database). The dependency rule says that code may only point inward, never outward. That already implies an acyclic graph. There is no way for an inner ring to depend on an outer one without violating the rule.

flowchart TB
  subgraph borda["Edge"]
    FW[Frameworks / UI / DB]
  end
  subgraph adaptadores["Adapter interfaces"]
    AD[Adapters]
  end
  subgraph aplicacao["Application"]
    UC[Use cases]
  end
  subgraph nucleo["Core"]
    ENT[Entities]
  end
  FW --> AD
  AD --> UC
  UC --> ENT

(Arrows indicate dependency direction: the edge depends on the core, never the reverse.) The ADP, in this context, is the expression of the same constraint at the scale of packages or modules. Even within a ring, packages must not form cycles. Boundaries between contexts or subsystems thus gain a notion of stability. Stable components (domain, core) do not depend on unstable ones (frameworks, details), and the graph remains orderable and predictable.


ADP and Domain-Driven Design: bounded contexts and semantic coupling

In Domain-Driven Design the system is decomposed into bounded contexts, each with its own model and ubiquitous language. The relationship between contexts is made explicit by a context map, which shows who depends on whom and under which pattern (conformist, anticorruption, etc.). Cycles between bounded contexts create semantic and organizational coupling. Semantic coupling arises because the models end up influencing each other. Organizational coupling, because teams have to coordinate changes on every update. A cycle between contexts exposes poorly defined boundaries or overlapping responsibilities. Two contexts that reference each other cannot evolve or be deployed independently, and the boundary between them becomes unclear. Eric Evans, in Domain-Driven Design: Tackling Complexity in the Heart of Software, emphasizes the importance of delimiting contexts and defining directional relationships between them. The ADP operates at this level. The dependency graph between contexts must be acyclic.

The Anti-Corruption Layer (ACL) does not eliminate dependency cycles that already exist between contexts. If B already depends on A, putting an ACL inside A does not remove B’s dependency on A. The cycle remains. The ACL is for when there is (or is being designed) a unidirectional dependency from A to B: it prevents A from depending on B’s internal model, keeping boundaries clean and the contract stable. In that scenario, A defines a translation layer that consumes B through a stable interface and adapts the data to A’s model. B does not need to know A and the context map remains a DAG because the direction was already unique. To satisfy the ADP when a cycle already exists between contexts, refactoring is required: extract a third context that holds what is shared or introduce asynchronous communication and events, so that the dependency graph ceases to be cyclic.

flowchart LR
  subgraph ctxA["Context A"]
    A[Model A]
    ACL[ACL]
  end
  subgraph ctxB["Context B"]
    B[Model B]
  end
  A --> ACL
  ACL --> B

(Acyclic context map: A depends on B only via Anti-Corruption Layer. B does not depend on A. The diagram only holds when the direction is already unique. An existing cycle does not disappear by adding an ACL.)

Reminder: The ACL does not fix existing cycles. It only prevents new cycles from arising when the dependency is already unidirectional.

When a cycle appears between bounded contexts, there is almost always a violation of the model autonomy principle. Refactoring to extract a third context or introduce asynchronous events restores acyclicity.

The same idea of an acyclic graph reappears in microservices, with services in place of contexts.


ADP in microservices: dependencies between services and communication

In microservice architectures the "package" becomes a service and dependencies become API calls, messages or events. Cycles between services appear when service A calls B, B calls C and C calls A, or when there are synchronous dependencies in a chain that close in a loop. Consequences include cascading failures, inability to deploy independently and version coupling. You cannot evolve one service without coordinating with all others in the cycle.

flowchart LR
  subgraph ciclo_svc["Cycle between services"]
    SvcA[Service A] --> SvcB[Service B]
    SvcB --> SvcC[Service C]
    SvcC --> SvcA
  end
flowchart TB
  P[Producer] --> EB[Event Bus / Broker]
  EB --> C1[Consumer 1]
  EB --> C2[Consumer 2]
  style P fill:#e8f5e9

(Left: synchronous dependencies in a cycle. Right: producer and consumers decoupled in time. At build time the dependency graph between services can be acyclic. At runtime care must be taken to avoid event chains that close in a loop.)

Synchronous communication (HTTP, gRPC) tends to expose direct dependencies and make cycles easier when teams do not enforce a clear direction. Event-based architectures (publish/subscribe, event sourcing) decouple producers and consumers in time. The producer emits an event and does not know who consumes it. The dependency graph at compile or build time is what the ADP requires to be acyclic. At runtime there can still be event cycles if a chain of publications and consumptions closes in a loop. If no consumer depends on the producer in a synchronous, bidirectional way, cycles between services tend to decrease. Service mesh and network policies can enforce rules about who may call whom, aligning topology with the desired graph (DAG). Centralized orchestration (an orchestrator that calls several services in sequence) avoids cycles because the orchestrator is the single root that depends on the others. Choreography (services that react to events and trigger other events) can preserve acyclicity as long as there is no event chain that closes in a loop.


ADP and DevOps: pipelines, build and deploy

CI/CD pipelines assume that parts of the system can be built and tested incrementally. When the repository or the module graph contains cycles, that assumption fails. Any change at a node in the cycle forces rebuilding and retesting the whole cycle. Build time increases, the feedback loop lengthens and independent deploy per service or module becomes unfeasible. Modern build systems (Bazel, Buck, Nx, Turborepo) model the workspace as a DAG of tasks. When the code violates the ADP, the task graph requires workarounds (e.g. lumping cyclic modules into a single target), which masks the problem instead of solving it.

In monorepos the ADP applies to packages or workspaces. The dependency between projects must be acyclic so that build tools can order and cache correctly. In multirepos each repository is usually a node. Dependencies between repos (via published versions) should also avoid cycles, or publishing and consuming libraries becomes a game of "who publishes first". Semantic versioning and directional dependencies (lib-core does not depend on lib-app) help keep the ecosystem as a DAG. Incremental deploy and segmented rollback depend on deploy units with stable boundaries and no cycles. Otherwise, "deploy only service X" can break the system because X is part of a dependency cycle at runtime or build time.

DAG for deploy in microservices and antipatterns

In CI/CD, a DAG is used to order deploy based on dependencies: each node is a service or job, each edge means "must be deployed before". When these dependencies reflect service coupling rather than legitimate technical dependencies, antipatterns arise. The table below summarizes the most common ones:

Antipattern Description Relation to the deploy DAG
Deployment coupling / Shared deployment Services depend on each other for deploy, breaking autonomy The DAG forces B to go to production only after A, even when A’s API is stable
Distributed monolith Microservices tightly coupled, not independent A complex deploy DAG indicates that services cannot be updated individually
God service / Big Ball of Mud One central service dictates the pace of the others Many edges converging on the same service: single point of failure and coordination
Tight version coupling Services must be deployed together because of API or schema version DAG forces synchronized versions; changing one service requires redeploying all dependents
Sequential pipeline bottleneck Long and fragile deploy chain A very deep DAG increases deploy time and risk of cascading rollback

The ADP, in this context, reminds us that a healthy deploy DAG should reflect real, minimal dependencies, not coupling that could be removed with better context design or asynchronous communication.


Organizational dimension: Conway’s Law and sociotechnical cycles

Melvin Conway observed in 1967 that the structure of systems tends to mirror the communication structure of the organizations that produce them. Conway’s Law is often quoted as:

"Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations." — Melvin Conway

If teams are organized in silos that need to approve each other for any change, the system tends to exhibit circular coupling between the modules each team owns. Technical cycles between packages or services often reflect organizational cycles, as when two teams depend on each other to deliver value without a clear order of who "feeds" whom. Breaking technical cycles sometimes requires realigning ownership, team boundaries and contracts between teams. Architecture is not only code. It is also how teams communicate. The ADP, in this sense, is not only a code rule. It is a constraint that, when taken seriously, pressures the organization to define clear directional dependencies and to avoid anyone being a blocker for everyone. Architectural governance that enforces dependency review and rejects merges that introduce cycles helps keep both the code graph and the team map consistent with a DAG.


Strategies for breaking cycles

Breaking a dependency cycle requires introducing at least a new direction or a new node that absorbs the mutual dependency. The most used techniques are dependency inversion and abstraction extraction. In inversion, one module in the cycle stops depending on the other directly and instead depends on an interface. The interface implementation lives in the other module (or a third one) and the dependency flow becomes unidirectional.

flowchart LR
  subgraph antes["Cycle"]
    M1[Module A] --> M2[Module B]
    M2 --> M1
  end
flowchart LR
  M1[Module A] --> I[Interface]
  M2[Module B] --> I

(Before: A and B depend on each other. After: both depend on the interface, which can live in a neutral package or in one of the modules, and the cycle disappears.) In extraction, the common code that motivated the circular dependency is moved to a new stable package, which both sides come to depend on. The cycle goes away and the new package becomes a leaf or intermediate node in the DAG.

Other approaches include interface segregation. Instead of a module depending on another as a whole, it depends only on a thin interface, which can live in a neutral package. Introducing a new component (event bus, facade, orchestrator) that becomes the single point that knows both sides of the cycle also breaks the circularity. The original modules come to depend only on the new component, which does not depend on them in terms of build. Event-driven architecture can replace synchronous calls with event publication so that the producer does not depend on the consumer in the compilation graph.

Each technique has trade-offs. Inversion and abstraction extraction increase the number of interfaces and sometimes packages. In excess they can lead to overengineering. The criterion is to break the cycle at the lowest cost in understanding and maintenance. In legacy systems, breaking cycles can be done incrementally. First stabilize one module in the cycle by extracting interfaces, then migrate consumers, and so on until the graph is acyclic.


Detecting and visualizing cycles

The strategies in the previous section require knowing where the cycles are. This section is about how to find them. Cycles can be detected by static analyzers that build the dependency graph from imports, type references or build metadata. Tools such as JDepend (Java), madge (JavaScript/Node), go-callvis (Go) or analysis modules in IDEs compute SCCs or list packages involved in cycles. Build tools (Maven, Gradle, Bazel) sometimes fail on resolution or ordering when there is a cycle, or offer plugins that generate dependency reports. Graph visualizers (Graphviz, dependency-graph in various stacks) let you inspect the DAG or identify clusters that form cycles. Interpreting strongly connected components with more than one vertex amounts to listing the cycles. From there the team can choose which edge to break (which dependency to remove or invert) based on cohesion and stability. When this check is automated in the pipeline (e.g. failing the build or blocking the merge if a cycle is introduced), it acts as a fitness function: an objective criterion that prevents the architecture from regressing in that dimension.


Systems that evolved without dependency rules tend to exhibit a cyclic package graph. Many cycles among many packages, with no clear layer or order. This pattern is related to the Big Ball of Mud, in which the structure degrades until any change touches the whole system. Spaghetti architecture is the manifestation in code, with flows that cross layers in every direction. Abusive shared kernel (a shared package that everyone imports and that also imports many of them) is another common source of cycles. The kernel stops being stable and becomes the central node of a strongly connected component. Warning signs include builds that recompile most of the project after a small change, tests that need to run in a specific order or fail when run in isolation, and difficulty describing "who depends on whom" without drawing a tangle. Metrics such as number of non-trivial SCCs, size of the largest cycle or graph depth help track degradation and prioritize refactorings.


ADP, Stable Dependencies Principle and Stable Abstractions Principle

As mentioned at the start of the article, the ADP does not stand alone in Martin’s proposal. It forms, together with the Stable Dependencies Principle (SDP) and the Stable Abstractions Principle (SAP), the set of package design principles. The SDP says that a package should depend only on packages more stable than it (that change less). The SAP says that stable packages should be abstract and unstable packages may be concrete. Together they guide a graph in which dependencies point toward stability and abstraction, with no cycles. The ADP guarantees the shape of the graph (DAG). The SDP and SAP guide the content and direction of the edges. There is no conflict among the three: an unstable package that depends on a stable, abstract one is in line with all of them. In practice, the trio helps keep the graph not only acyclic but stable and evolvable. The SOLID principles operate at the level of classes and objects. The ADP, SDP and SAP operate at the level of packages and modules, complementing design at scale.


Case study: legacy system with cycles between modules

A typical scenario is a medium-sized monolith split into packages by domain (orders, customers, inventory, payments). Over time the orders package came to need rules that lived in customers (profile-based discounts). Customers came to need history that lived in orders. Inventory started emitting events that orders and payments consume, but also querying orders for reservation. The dependency graph developed cycles between orders, customers and inventory.

flowchart LR
  P[orders] --> C[customers]
  C --> P
  E[inventory] --> P
  P --> E

(Cycles orders↔customers and orders↔inventory.)

After refactoring, extracted interfaces and orchestration break the cycles:

flowchart TB
  APP[application / use cases]
  P[orders] --> APP
  C[customers] --> APP
  E[inventory] --> APP
  style APP fill:#e3f2fd

The symptoms were well known: full build took several minutes after any change in one of these packages, integration tests needed the whole context up, and deploy was always "all or nothing". The refactoring consisted of (1) mapping the graph with a static analysis tool and listing the cycles, (2) extracting interfaces for "order history" and "discount rules" into an application package that both orders and customers came to use, removing the direct dependency between them, and (3) moving inventory reservation orchestration to a use case that depends on orders and inventory, without inventory depending on orders. After breaking the cycles the build became incremental for most changes and teams could work on orders and customers with fewer conflicts. The cost was a few more interfaces and a more explicit application package. The gain was restored clear boundaries and predictable build time.


Economic dimension: cost of change, technical debt and metrics

Cyclic dependencies increase the cost of change. Any change at a node in the cycle may require retesting and redeploying the whole cycle. This cost becomes technical debt when the team postpones refactorings for fear of breaking something far away. Flow metrics such as lead time (time from start to delivery of a change) and MTTR (mean time to repair) worsen when the system is a tangle. Changes take longer and fixes have unpredictable side effects.

Scenario Lead time MTTR Deploy / change
Cyclic graph High (change propagates in many directions) High (fixes with side effects) "All or nothing", rollback difficult
Acyclic graph (ADP) Lower (stable boundaries) Lower (localizable impact) Incremental, rollback per component

The ADP contributes to long-term sustainability by keeping the graph orderable and boundaries stable, reducing the marginal cost of each new feature and easing onboarding and maintenance. From an economic standpoint, investing in acyclicity is investing in future options. The system remains evolvable without each step requiring large-scale rework.


Relevance in distributed systems, events and modular ecosystems

In distributed systems the ADP shows up in the topology of services and data flows. Event-based architectures (event sourcing, CQRS, pub/sub) tend to produce graphs in which producers and consumers are decoupled in time. The dependency of "who consumes what" can be modeled as a DAG if no consumer is also a producer of an event that the original producer consumes in a closed loop. Modular platforms and plugin ecosystems depend on modules and extensions declaring unidirectional dependencies. Cycles between plugins or between platform and plugin make loading and updating unpredictable. In AI and autonomous system scenarios, data and model pipelines are often described as DAGs (e.g. task DAGs in orchestrators). The same idea of ordering and absence of cycles applies to ensure deterministic and reproducible execution. The principle tends to remain relevant whenever system evolution and operation depend on a clear order of dependencies and well-defined boundaries.


Visual summary: from cycle to DAG

The diagram below summarizes the article’s central idea. A cyclic graph (monolith, microservices or bounded contexts) yields high cost of change and unstable boundaries. Break techniques (dependency inversion, abstraction extraction, third context, events) remove cycles and lead to a DAG, with clear ordering and incremental builds or deploys.

flowchart LR
  subgraph problema["Problem"]
    C[cycle between packages / services / contexts]
  end
  subgraph tecnicas["Break techniques"]
    T1[Dependency inversion]
    T2[Abstraction extraction]
    T3[Third context / events]
  end
  subgraph resultado["Result"]
    D[DAG: topological order, incremental build/deploy]
  end
  C --> T1
  C --> T2
  C --> T3
  T1 --> D
  T2 --> D
  T3 --> D

Conclusion

The Acyclic Dependencies Principle is not an arbitrary style rule. It is the requirement that the system’s dependency graph be a DAG, enabling topological ordering, incremental builds and stable boundaries between components. It applies to packages in a monolith, to bounded contexts in DDD, to services in a microservice architecture and to tasks in build and data pipelines. Respecting it reduces accidental complexity, aligns technical and organizational structure (Conway’s Law) and lowers cost of change and technical debt. Breaking cycles requires techniques such as dependency inversion and abstraction extraction, with trade-offs between number of interfaces and clarity. Detecting cycles is possible with static analysis and build tools. Together with the Stable Dependencies Principle and the Stable Abstractions Principle, the ADP forms a foundation for package and module design that remains relevant in layered architectures, Clean Architecture, DDD and event-oriented distributed systems.

One practical step: in your next architecture review, run the dependency analysis for your project (JDepend, madge, go-callvis or the equivalent in your stack) and check whether any non-trivial SCC exists. If it does, ask: which edge makes the most sense to break first, based on cohesion and stability?


References

  • Martin, Robert C. Granularity: The Acyclic Dependencies Principle. 2002. (Article on package design and ADP.)
  • Martin, Robert C. Agile Software Development: Principles, Patterns, and Practices. Prentice Hall, 2002.
  • Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
  • Conway, Melvin. How Do Committees Invent? Datamation, 1968. (Origin of Conway’s Law.)