Create the Library Boundary In short: A library is not a folder people agreed to be careful with. It is a project the build system knows about, with tags declaring what it may depend on, and a lint rule that fails when someone crosses the line. If is just a directory where developers put components when two apps need them, it will accumulate business logic. Not because anyone is careless — because there is no moment at which the wrong thing is harder to do than the right thing. Boundaries have to be physical. Generate a real library The first step is that the library is a separate project , not a subfolder of an app. It gets its own build, its own test target, and its own . That gives you: And a path mapping so consumers import by package name rather than by relative path: That mapping matters more than it looks. It means an application writes: and cannot write: …which is why the path mapping alone is not enough. We need the lint rule below. The layering model An enterprise workspace separates four layers. For our Dairy Logistics ecosystem: Layer Contains Example --- --- --- App Deployable applications; routing and composition , Feature Smart components, state, API orchestration Domain Business types and rules, no UI , UI Presentational components, no business context Util Pure helpers with no Angular dependency And the rule that holds it together, from Chapter 2: Dependencies point one way. A feature may import UI. UI may never import Domain. A shared button must not know what "Grade A" means. Tag every project Two dimensions: which domain owns it, and where it sits in the layering. Make the boundary fail the build Now inside fails lint. The build goes red. The boundary holds. If you are not on Nx, does the same thing using path patterns. Closing the loophole the tags miss The failure from the previous lesson — the dropdown that special-cased — was not an import. It was a string literal. No dependency existed, so no boundary rule fired. Catch it directly: It is a blunt instrument, and it will occasionally flag a comment. That is an acceptable trade: the message tells the author what to do instead, which is how a good guardrail behaves. Also constrain the runtime dependencies One more rule stops the "dependencies leaked out" failure. Library code should not reach for application infrastructure: A shared component that injects is unusable in an application with different routes — which is to say, unusable in the second application. Blocking it now is much cheaper than discovering it in Chapter 7. "But I thought a folder plus a code review convention was enough" Plenty of teams run this way successfully for a year or two, so the belief has real evidence behind it. It usually holds while the library has one or two maintainers who wrote most of it and notice everything. It breaks on three things, none of which are about discipline. Convention has no error message. When a developer imports into a shared component, nothing tells them. Their editor autocompleted it, TypeScript resolved it, the app ran. The first feedback arrives in code review, if the reviewer looks at the import block — and by then the developer has built on top of it. Conventions are unevenly known. The person who set the rule explained it to the four people in the room. The team is now nineteen people, three of whom joined last month, plus contractors, plus the person from billing making a one-off fix. None of them read the ADR from 2024. One exception becomes the rule. Someone needs in a shared badge on a deadline. It ships with a comment. Six months later there are four business types in the library, and each new one is justified by the existing ones. There is no longer a rule to enforce. The difference in practice, over a year on a library with roughly 500 pull requests: Folder + convention Tagged project + lint --- --- --- Where the developer finds out Code review, hours later The editor, immediately Boundary violations merged per year 10–20 0 Depends on who reviewed Yes No Survives team turnover No Yes Effort to add a new rule Persuade nineteen people One config line Relative import bypass ( ) Works Blocked That last row is the one people miss. Even with a mapping, a relative import into the library's internals resolves perfectly well. Only the lint rule closes it. Convention is how you decide what the boundary should be. Configuration is how it stays that way. Cheat sheet Boundary Enforced by --- --- UI cannot import Domain , tags Tanker cannot import Billing , tags No business words in library code on string literals No or in UI No reaching past Path mapping + patterns No new exports without review CODEOWNERS on Recap Make the library a real project , with its own build, , and path mapping. Tag by domain and by layer , then declare what each may depend on. UI may only depend on UI and Util. That single constraint is the zero-business-context rule. Tags miss string literals. Add a vocabulary rule, with a message that says what to do inste…