Custom code that ignores Magento's patterns ages badly
Magento 2 is built around service contracts and dependency injection, and custom code is expected to use them. A lot of custom code does not, reaching around the framework instead, and that code ages badly.
Ignoring service contracts and proper dependency injection produces modules that are fragile on upgrades, hard to test, and tightly coupled to internals that Magento controls. The shortcut works today and costs later.
This article covers what service contracts and dependency injection are for, how custom code goes wrong without them, and how to recognize the patterns in an audit.
What service contracts are for
Service contracts are Magento's stable interfaces for working with its data: repositories and API interfaces for products, orders, customers, and more. They exist to give custom code a stable way to interact with the platform.
Code that uses a repository to load and save a product is insulated from how Magento stores that product internally. When the internals change in an upgrade, the contract stays stable, and the custom code keeps working.
This stability is the whole point. Service contracts are the promise Magento makes about what will not change, so custom code has firm ground to stand on.
What dependency injection provides
Dependency injection is how Magento provides objects to the code that needs them. A class declares its dependencies in its constructor, and Magento's object manager supplies them.
This makes code modular and testable, because dependencies can be substituted, and it lets Magento's plugin and preference systems work. Code that creates its own dependencies directly, instead of declaring them, opts out of all of that.
Proper dependency injection is not ceremony. It is what makes a module fit into Magento's architecture rather than fighting it.
How custom code goes wrong
Custom code goes wrong when it bypasses these patterns. Instead of a repository, it queries the database directly; instead of declared dependencies, it uses the object manager directly or instantiates classes by hand.
Each of these shortcuts couples the code to internals it should not touch. The module works in isolation and breaks when the internals it reached into change.
The upgrade cost
The bill comes due at upgrade time. Code that used service contracts is insulated from internal changes, and code that reached around them is not.
A module querying the database directly, or depending on internal classes, breaks when those change, often silently. The upgrade that a well-built module survives untouched becomes a debugging session for one that ignored the patterns.
This is why upgrade cost correlates with code quality. The modules that took shortcuts are the ones that need attention every time the platform moves.
The testability cost
Code that ignores dependency injection is hard to test. When a class creates its own dependencies, they cannot be substituted with test doubles, so the class cannot be tested in isolation.
This means the code tends to be untested, which compounds its fragility. Fragile code without tests is exactly the code that breaks quietly on upgrades, because nothing catches the regression.
Proper dependency injection makes code testable, and testable code is code you can change with confidence. The shortcut that skips it also skips the safety net.
Recognizing it in an audit
These patterns are findable. Direct object manager usage, direct database queries in place of repositories, and classes instantiated by hand rather than injected are all searchable in the custom code.
Grepping for direct object manager calls and raw database access surfaces the modules that took shortcuts. Reading how a module loads and saves data, and how it obtains its dependencies, shows whether it works with Magento's architecture or around it.
The result is a map of which custom modules are built to last and which are upgrade liabilities. That distinction is exactly what an audit is trying to draw.
The object manager exception
There is one nuance worth stating clearly. Direct use of the object manager is acceptable in a few specific places, such as certain factory and proxy situations, where Magento itself uses it.
The problem is direct object manager use in ordinary business logic, where constructor injection was the right choice and was skipped. That is the pattern that signals a shortcut, not the rare legitimate use.
Knowing the difference keeps the review accurate. The finding is not every object manager call, it is the ones in normal code that should have declared their dependencies and did not.
Built to fit, or built to break
Custom code that uses service contracts and dependency injection fits into Magento and survives its changes. Code that ignores them is coupled to internals it should not touch, and it breaks when they move.
Knowing which of your custom modules follow the patterns and which reach around them predicts your upgrade cost and your fragility. Finding that distinction is a core part of a module-quality and security review.