From 3711331bdd59967f9259bddf0b3811754ec39c63 Mon Sep 17 00:00:00 2001
From: Marco Pivetta
+ For non-Laravel developers: beware that this is not about the facade pattern.
+
+ Recently, I had a heated discussion with a software developer: they approached me, asking
+ me why I had flagged all usages of Laravel facades in a code review.
+
+ Laravel Facades are a pattern that has been deeply ingrained in the development practices of teams that
+ solely design Laravel code, and they are mostly promoted in terms of DX,
+ rather than technical merits.
+
+ Following is a piece of code that uses a Laravel Facade to store some data in cache:
+
+ From an inexperienced observer's point of view, this code is relatively straightforward: it
+ does some heavy work, it stores the information in a cache, then returns.
+
+ @TODO we need a stronger example here: what's a good "whoops" example, like a checkout on the wrong banking coordinates?
+
+ @TODO expose how this problem occurs especially when:
+ * an application grows in size, accommodating for multiple service instances
+ * tests: multiple services needed, one per test!
+
+ A second problem occurs when running this code in isolation: assuming you have **only**
+ autoloading configured, what will it do?
+
+ This kind of crash is very similar to what you'd experience with the service location pattern: since
+ you are deferring all instantiation to the point at which a dependency is effectively invoked, you
+ may run in crashes due to a missed dependency.
+
+ In addition to the disadvantages of service-location, you also have the hidden dependency of the facade's
+ internal
+
+
+ Your code cannot work until you've bootstrapped a Laravel application, which is a complex and heavy
+ (performance-wise) operation.
+
+ Another issue with our
+ This is both an issue of Laravel, which exposes a very bloated
+ Coming myself from the
+ DDD
+ community, isolating software dependencies is critical for the long-term maintenance of a system, and reducing
+ the amount of dependencies is always a good idea, as it is entropy that will easily spin out of control.
+
+ In this case, we wanted a cache, not the entire framework.
+
+ @TODO expose here how adding a facade introduces magic method calls, stack frames, static analysis complexity.
+
+ @TODO expose concept of "simple" != "easy". Systems can be complex and easy, or harder to use, but simple.
+
+ Here's our service re-implemented to make things simple and explicit:
+
+ The above is a little more verbose, but follows very old and well-functioning
+ dependency injection rules.
+
+ By adding 2 lines of code, we:
+
+ Does the above solution have worse DX?
+ Given all the listed advantages, I think it provides much better experience.
+
+ You have to also remember that facades were designed and built in an age when auto-wiring dependency injection
+ containers weren't common in PHP: the simpler approach may actually even be easier, when using the full framework.
+
+ @TODO Laravel has MyFacade::spy() and MyFacade::mock() helpers - let's document those
+
+ The above example swaps the facade underlying service location mechanism with a mock, and relies on an
+
+ extremely complex base test class
+ to do framework startup/cleanup operations.
+
+ Here's the "simple" version instead:
+
+ Notice how we got rid of:
+
+ Don't like using automatic mocks by PHPUnit? Bring your own!
+
+ The above is obviously brought to extremes, but it highlights the added degrees of fredom that are introduced.
+
+ Now imagine having to upgrade a test suite with 5000 tests, all depending on Facade spies
+ and
+ From my point of view, Facades are technical debt, and of a particularly bad and sneaky kind.
+
+ There is no reason to skip the extra legwork to keep a system simple: systems increase complexity
+ over time by nature, and it is our job as software designers to keep it at bay.
+
+ Introducing complexity for the sole purpose of some very questionable DX
+ claims is therefore not acceptable, and is not something I accept in software systems that I manage.
+
+
For non-Laravel developers: beware that this is not about the facade pattern.
The Example
+
+Problem 1: shared mutable state
+
+~~~php
+doSomeHeavyWork();
+$result2 = $service2->doSomeHeavyWork();
+~~~
+
+Problem 2: hidden dependencies
+
+static::$app
+ .
+Problem 2: framework dependencies added
+
+MyService is that we have widely expanded the contact surface with the
+ framework: our code now depends on illuminate/support, and also on illuminate/contracts
+ in order for it to function at basic level.
+illuminate/contracts package, and
+ of our code, since framework upgrades can now affect our ability to perform upgrades.
+Problem 3: hidden complexity
+
+
The simple solution
+
+
+
+
+But what about my test helpers?
+
+
+
+
+Illuminate\Foundation\Testing\TestCase: sounds fun? No? I had to do it, a few times,
+ and I can also assure you it's not fun.
+Disallowing facades
+
+
+ @TODO expose how the facade is "hidden API" - not all interactions with the object are exposed by the public + class signature anymore, while constructor shows clear inputs/outputs. +
+