Passing the Benchmarking Torch After 7 Years

Long-time readers will recall that I am interested in performance benchmarks as a tool to help discover the outer limits of framework responsiveness. See the blog category along with the most recent measurement report and the GitHub repo for replicating the results on your own.

Benchmarking is useful because it helps you decide if it makes more sense to work on improving your application, improving your framework, or improving your server. If the maximum dynamic responsiveness of the framework in question is 200 req/sec, and you need 300 req/sec, then there is no code you can add in your application to make it go faster in a dynamic scenario; you will have to look at the framework or the server. (Lazy reader who considers himself an unappreciated genius interjects: “But you can cache it!” I said a dynamic scenario, not a static one.)

There’s a lot of emotion and drama associated with benchmarking. The subjects that come in “first place” too often point to it as SCIENCE PROVES WE ARE BEST and the subjects that come in “last place” respond with variations of THIS IS STUPID AND PROVES NOTHING. (That is, until the last-placers run their own benchmarks where they come in first, and suddenly it’s a great marketing point. I’m looking at you, Symfony.) The point in benchmarking is to add information to your decision-making process so you can make better use of your limited resources of time and effort, and choose between competing tradeoffs in a more informed way. Speed alone over-and-above anything and everything is for suckers; it’s an important point, not the important point, when evaluating tradeoffs.

Leaving the elements of drama aside, benchmarking properly is difficult and time-consuming work. For my own limited benchmarks, it took three days or more to properly update, test, run, fix, and re-run to perform them well, even with automated scripts to do the setup and analysis. And that was for the most basic bare bones “hello world” that benchmarks only the dynamic dispatch cycle (bootstrap, front controller, page controller, action method, and view rendering).

Enter the guys at TechEmpower.

They’re doing a series of regular benchmarks that includes not just a double-handful of PHP frameworks, but 90 frameworks/languages/foundations across several languages. They do the basic “hello world” bench in addition to a few others, such as ORM/database speed. They appear to share an approach similar to the one I first published in Nov 2006 and improved with the help of Clay Loveless in Jan 2007. The TechEmpower motivations appear to the be similar to mine as well. They have equalled and then exceeded the efforts that I’ve been able to put forth on my own. From what I can tell it’s really good work.

With that, I am happy to say that I will be retiring my benchmarking project in favor of the TechEmpower one. Until futher notice, I’ll be combining my efforts (such as they may be) with the TechEmpower folks.

Which is Lighter, Silex or Aura.Web_Project?

Always remember that there is no such thing as “bloated” or “heavy” or “light”; there is only “heavier” or “lighter” or “more bloated” or “less bloated” in comparison to something else.

… let’s go with that article [on Silex] and use its approach to make a comparison between Silex and Aura.Web_Project to see if my earlier claim, using the terms and measurements outlined by the Silex post author, is accurate.

  • If you measure by the number of package dependencies, disregarding how similar functionality is split up between packages, then Silex is lighter.

  • If you measure by the total disk usage, Aura.Web_Project is lighter.

  • If you measure by the total NCLOC, Aura.Web_Project is lighter.

  • If you measure by the total class count, Aura.Web_Project is lighter.

  • If you measure by the acutual-usage class count, disregarding how similar functionality is split up among classes, then Silex is lighter.

  • If you measure by the actual-usage NCLOC, Aura.Web_Project is lighter.

  • If you measure by how many classes “for the most part” are in the public API, Aura.Web_Project is lighter.

In 5 of the 7 measures put forth by the original Silex article, Aura.Web_Project is the lighter of the two.

UPDATE: Via Which is Lighter, Silex or Aura.Web_Project?

Quicker, Easier, More Seductive: Names, Usage, and Intent

Yesterday’s post in this unexpected series on Dependency Injection vs Service Locator generated a lot of excellent and productive feedback in Twitter, on the PHP-FIG mailing list, and in the comments.

In that post, I opined that there was a significant difference between how Service Locator containers and Dependency Injection containers are implemented. This came from my sense that because they have different names they must be different things, and so I had been trying to find a way to discern the difference by looking at the implementations.

They’re Different Names For The Same Thing …

As the disucussion progressed, it became more clear to me that there really is no significant difference in how Dependency Injection containers and Service Locator containers are written. They are both Inversion of Control (IOC) containers, and are not distinguishable by their code, API, features, etc. (although some may have more or fewer features than others).

As such, the terms Dependency Injection and Service Locator appear to be interchangeable in the sense that a container is a container is a container. The difference in naming comes from how the container is used, not how the container is implemented:

  • When the container is used outside a non-Factory object, you are using the container for Depedency Injection.

  • When the container is used inside a non-Factory object, you are using the container as a Service Locator.

(Side note: Using the container inside a Factory object does not seem to be a case of either Dependency Injection or Service Locator specifically; it’s just a generic container usage at that point. This stems from the idea that object creation should be separated from every other type of object operation; that is, a class should either create an object, or it should operate on objects, not both. Factory, Builder, etc. classes are thereforce special cases.)

… But The Names Still Indicate A Difference

This still leaves me with a trio of related issues: usage, naming, and intent.

I am big on trying to use the right names for things. I think it’s important for clear, consistent communcation that when Developer A uses a word or phrase to describe something, Developer B should be able to understand what Developer A is getting at. The need for common understanding should be so obvious as to not require pointing out. With that in mind …

  • If an author creates a “Dependency Injection” container but his intent is for it to be injected into non-Factory objects so the objects can retrieve their dependencies, he has named it incorrectly. He can correctly call it a “Container” or “IocContainer” in general, or a “Locator” or “Service Locator” in specific, but his intent and usage are clearly at odds with his naming. (Closures and functions count here, too. Injecting a “Dependency Injection” container into a closure or function is a Service Locator usage of that container.)

  • Similarly, if a user uses something labeled as a “Dependency Injection” container by injecting it into his non-Factory objects so they can retrieve their dependencies, his use is at odds with the naming and intent of the container. He is using it as a Service Locator.

  • Conversely, if an author creates a “Service Locator” that is used only in Factory objects, and never in other kinds of objects, the name is at odds with the intent and usage as well. That’s a “Dependency Injection” container, or more generically a “Container” or “IocContainer”.

  • Finally, if a user uses something labeled as a “Service Locator” and never injects it into his non-Factory objects, his use is at odds with the naming and intent of the container. He’s using it for Dependency Injection.

Conclusion

As a result of all this, I am left with the conclusion that container authors should be very careful about their naming, and that container users should be disciplined about their usage, both based on the intent indicated by the container’s name:

  • If the author intends the container to be injected into non-Factory objects, including closures and functions, call it “Locator” or “Service Locator”. Users should honor that intent.

  • If the intent is that the container never be injected into non-Factory objects, including closures and functions, call it “DI” or “Dependency Injection”. Users should honor that intent.

  • If the author has mixed intent, call it a generic “Container” or “IocContainer”. Users can mix Dependency Injection and Service Locator in Factory and non-Factory objects and closures.

Again, the point here is to make sure that other people know what we’re talking about when we use these terms. Using the wrong names leads to confusion regarding usage and intent.

Afterword

Are you overwhelmed by a legacy PHP codebase? Do you want to improve it, but don’t know where to start? My book, Modernizing Legacy Applications in PHP, will lead you step-by-step through a series of small, incremental changes that will dramatically improve the quality of your legacy codebase.

Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator

(In this unexpected series, we’ve been talking about the use of Service Locator versus Dependency Injection.)

UPDATE: Please see the next post in this series for important corrections and updates regarding the essay below.


It is easy to confuse a Dependency Injection container with a Service Locator. They are very similar to each other. The differences are subtle. Indeed, it’s even possible to use a Dependency Injection container as a Service Locator, although I think it’s difficult to use a Service Locator as a Dependency Injection container. They are both sub-patterns of a more generic pattern called Inversion of Contol, and some people confuse the term Dependency Injection with the more generic term Inversion of Control.

How To Tell The Difference

I’ve been thinking about what heuristic or rule-of-thumb one could use to easily tell which is which. So far I’ve been leaning in the direction of: “Can you create new instances without defining a service?” If you can, it’s a Dependency Injection container; otherwise, it’s a Service Locator. There are several variations on that prhasing:

  • If you can create a new instance of a class without having to define a service, it’s a Dependency Injection container.

  • If you have to define a service in order to get an object out of the container, it’s a Service Locator.

  • If you cannot create a new instance of a class without defining it as a service, it’s a Service Locator.

By this I mean that you should be able to repeatedly create new, discrete, independent instances of a class, without having to define it as a service in the container.

Examples

Given the above heuristic, here’s an example of Dependency Injection using Aura.Di:

// they are separate instances, not shared services
$foo_1 = $di->newInstance('FooBarBaz');
$foo_2 = $di->newInstance('FooBarBaz');
var_dump($foo_1 === $foo_2); // false

Might you need to set up constructor parameters, setters, etc. for the newInstance() calls? Sure; Aura.Di allows for inherited and overridable constructor parameters and setter methods:

// given __construct($gir)
$di->params['FooBarBaz']['gir'] = $di->newInstance('DibZimGir');

// given a setter method setOperation($operation_name)
$di->setter['FooBarBaz']['setOperation'] = 'ImpendingDoom';

The main point is that whenever the Dependency Injection container creates a class, it can do so without having to define that class as a reusable service.

In contrast, a Service Locator requires that you define a service, and then you can retrieve a shared instance of that service from the container. Here’s an example with my trivial Service Locator example from earlier in this series:

// define shared services
$locator->set('gir', function () use ($locator) {
    return new DibZimGir;
}

$locator->set('foo', function () use ($locator) {
    $foo = new FooBarBaz($locator->get('gir'));
    $foo->setOperation('ImpendingDoom');
    return $foo;
});

// these are shared services, not separate instances
$foo_1 = $locator->get('foo');
$foo_2 = $locator->get('foo');
var_dump($foo_1 === $foo_2); // true

Who’s Doing What?

I had a quick look at some containers in PHP land to see how well they fulfill the above heuristic; my assessment is as follows:

Some notes:

  1. Laravel: The documentation calls the container an “IoC” (Inversion of Control) container but mentions “Dependency Injection” throughout, never “Service Locator.” It is clear from the container’s usage throughout the rest of the codebase that it is in fact a Service Locator, since the container itself is injected into the classes that need services. This strikes me something that can be remedied with a documenation change, since the class itself is named in a generic way. UPDATE: the project lead has informed me of the public make() method on the container that creates new instances. Although I pick nits over the naming (using $class and newInstance() instead of $abstract and make() would make things more obvious IMO) it does fulfill the above heuristic.

  2. Pimple: Interestingly, Pimple has a factory() method that will return independent instance of a service. Some may think of this as meeting the heuristic above, but I don’t think it does; you still have to define the service, after all. I suppose one might define the service as a factory for a particular class name, and name the service for the class to be factoried, but that’s not quite the same; among other things, you still have to define a service for each class you want to factory.

  3. Slim: This uses $app as a Service Locator. To his credit, Josh Lockhart does not call it either a Service Locator or a Dependency Injection container, so kudos to @codeguy for avoiding the issue altogether.

  4. Symfony DependencyInjection component: My quick review of the codebase did not reveal a way to create a new instance of a class. Instead of a missing capability, It seems more likely to me that I just did not see it. If there are any Symfony guys reading this, please let me know how to create a new instance of a class with the Symfony DependencyInjection component, and I’ll update the description.

Conclusion

According to the heuristic above, some of the things calling themselves Dependency Injection systems are probably better described as Service Locators. They might be Containers (or Inversion of Control containers) in a generic sense, but they are not Dependency Injection containers. There’s nothing wrong with not-being a DI container, but using the wrong names for things leads to confusion on a topic filled with subtleties.

Of course, I’m sure that the folks using these Service Locator systems will have plently of rationalizations for why (1) the preferred system of their particular tribe really is a Dependency Injection container no matter what made-up rules Paul wants to apply, and (2) the distinction doesn’t really matter anyway, why even bring it up, just use their preferred system. I look forward to the comments on this one. 😉

Afterword

Are you overwhelmed by a legacy PHP codebase? Do you want to improve it, but don’t know where to start? My book, Modernizing Legacy Applications in PHP, will lead you step-by-step through a series of small, incremental changes that will dramatically improve the quality of your legacy codebase.

Lighter than Silex, and Slimmer than Slim: Aura.Web_Project, the Micro/Macro Framework

… the Aura.Web_Project package is a minimalist web-specific project framework that starts out small and grows only as you need it.

By “minimal” we mean very minimal. The project package provides only a dependency injection container, a configuration system, a router, a dispatcher, a pair of request and response objects, and a Monolog instance for logging.

This minimal implementation should not be taken as “restrictive”:

  • The DI container, coupled with the project kernel’s two-stage configuration, allows a wide range of programmatic service definitions. This means no more static calls to configure services; edit the modify stage config files, pull a service out of the container, and operate on it directly.

  • You can add any library you want into the project, not just Aura libraries, through Composer and the DI container. This means the bundle/plugin universe is made up of anything written in PHP, not just Aura-specific plugins.

  • As noted in earlier articles in this series, the router and dispatcher are completely separated from each other, and are built with iterative refactoring in mind. This means you can start with micro-framework-like closure controllers, and work your way into more complex controller objects of your own design.

Book Announcement: Modernizing Legacy Applications in PHP

There you are, working late for the second night this week. Everyone else in the office has gone home. Most of the lights are out. Yesterday it was trying to copy a feature over to a different section of the site, because marketing wanted their new client to see it there. Today, you have just finished tracking down a globals-related bug in an SQL query embedded in a page script. It revealed itself only after yesterday’s feature addition. You had to search across scores of PHP files to find it. You commit the change, push it to the common repository, and update the bug report so that QA can check on it.

Although you are relieved, you are still frustrated and worried. Who knows what break this fix will reveal? The work should not be this hard.

Your Legacy Application

You were so excited when you got the job. The interviews went well. You clearly knew more than almost everyone else there. The other developers seemed like they really needed your help and expertise. The managers gave you a new laptop and a new LCD screen so you could be as efficient as possible. It was the perfect setup. You were eager to get to work showing off your skills and abilities to make the application sing, and get the project back on schedule.

But then you checked out the code base. It was a mess. It had been architected over several years by mulitple different lead developers, and it showed. There was no consistent pattern to any of the structure. The oldest core of the system was a collection of “include” files that ran several levels deep. Later, someone had bolted on some class-oriented libraries, and third person has decided to try a framework rewrite. All of it was done in a different coding styles, with different naming conventions. The codebase is a mixed-up aggregagation of PHP, HTML, SQL, JS, and CSS, frequently all in the same file. And there are no tests at all! The “tests” are the QA team running over the site once or twice a week.

After that first day, when you felt so enthusiastic, you have been reduced to feeling like a brand-new beginner every day since. It’s humiliating. Each day has been a new frustration, a new “WTF?” moment replayed in a dozen different ways. You want very much to to improve the codebase as it is, so that you can impose some sense and reason onto it, but the code is such an overwhelming mess of spaghetti that you don’t even know where to start.

Overwhelmed and drowning in bad code that you inherited from others, you’re ready to give up.

Modernize Your Legacy Application

But what if I told you it didn’t have to be this way? What if I told you there was a specific series of small, incremental changes you could make over time to slowly make the codebase better, more modern, and thereby reduce your own sense that things are futile?

My upcoming book, “Modernizing Legacy Applications in PHP”, does exactly that. Using my talk “It Was Like That When I Got Here” as a starting point, I condense 15 years of fixing PHP codebases into a collection of specific steps to complete in order. As you apply these small fixes, each one building on the last, you will be able to restructure your codebase from a spaghetti mess to a testable, organized, modernized application, free of globals and mixed concerns.

Would you like to have that knowledge, distilled into an easy-to-understand book? Sign up on the mailing list below, and you’ll be the among the first to:

  • Find out how the book is going (and when it’s ready for purchase!)

  • Receive hints on how to improve your codebase now

  • Get a free pre-publication chapter

[mc4wp_form id=”5830″]

I promise, you’ll never receive any spam or other unwanted messages on this list. Add your email today!

Splitting The Blog

When I started this blog back in 2004, I wanted it to be a single location for me to publish on any and all topics that piqued my interest. However, events that I have planned for the future (more on that later!) make me think it would be wise to split the blog in two: one for more professional topics, and one for those that are more personal.

This existing .com blog will continue to be where I publish regarding technical, development, programming, management, and other professional topics. The new .org blog, will be “everything else,” including the political and economics posts that some of my readers love and others loathe.

The non-development-related posts on this blog have already been moved over to the .org location, and will disappear from this .com site soon. Short of adding rewrite rules for *every single post* that is now only over at the .org site, I can think of nothing that will keep existing links to those posts from breaking. My apologies to those of you who are affected. Comments, etc., will be retained.

Pretty soon, I’m going to change the hosting and the theme here as well. With any luck I’ll have an announcement thereafter for all the remaining technical readers.

Thanks for your patience, everybody!

UPDATE (9:48pm): Turns out it was relatively easy to select all the post IDs on the .org site and build a list of Apache “RedirectPermanent” directives on the .com site, like so:

RedirectPermanent /archives/6 http://paul-m-jones.org/archives/6

That’s courtesy of Apache mod_alias, not mod_rewrite, which is nice.

PSR-4 “Autoloader” Has Passed

I wrote up the initial “Towards A Package-Oriented Autoloader” essay on 26 Mar, and the first formal proposal document on 03 Apr. Counting from the date of that first formal proposal, it has taken exactly 8 months of discussions, one botched vote, one rescinded vote, an entirely new FIG workflow, and four or five rewrites to get PSR-4 passed. Maybe 8 months doesn’t sound so long when you look back on it, but while you’re in the middle of it, it’s interminable.

The core concept has not changed since the initial essay: provide a way to map the “namespace prefix” of a class to an arbitrary directory, to make directory structures shallower than they are under PSR-0. This means no more empty directories in Composer (etc) packages just to suit the strict class-to-file rule of PSR-0, which itself is descended from the Horde and PEAR standards by way of Solar, Symfony, Zend, and others.

The initial implementation of the ruleset has not changed in any major way in that entire time, although the language describing the ruleset has gone through several major variations.

For what it’s worth, the existing Aura v2 libraries are already structured according to the PSR-4 rules, and work with Composer through their own package-specific autoloader files:

Many thanks to everyone who helped bring the initial proposal to its completed state. In particular, thanks to Beau Simensen who (on what turned out to be the final major rewrite) came up with the idea of mimicking the PSR-0 language style. Also, thanks to Phil Sturgeon, who (as sponsor and vote coordinator) finally saw fit to let a vote actually go through. 😉

Quicker, Easier, More Seductive: The Difference Between Factories, Registries, and Service Locators

In last week’s episode of this unexpected series, I said, “Go ahead and use Service Locator, but make sure each Locator contains only one type of object. This condition of restraint will keep a single locator from becoming a menace.” (I recommend “real” dependency injection containers as the preferred inversion-of-control mechanism; try the Aura.Di container.)

The “only one type of object” constraint generated some discussion regarding factories and registries. On Reddit, teresko commented:

I don’t think I have ever seen a use of Service Locator where it adhered to this rule. Hell … I am not even sure that you can call it “Service Locator” then, because it is just a smarter sounding name for Registry pattern.

When you apply this rule, what you get instead is a factory, which enforces uniqueness of an instances, that it creates.

On the blog itself, Adrian Mu asked:

In the case of the Aura’s filter package am I wrong to interpret the “rule service locator” as a “rule factory”? If no, what is the difference between a factory and a service locator as you describe it? If yes, where?

And on Twitter, Taylor Otwell asked a similar question:

that “one type” of service locator is just a “factory”, right?

The differences between factories, registries, and containers (both service locators and dependency injection containers) can be subtle and hard to grasp at first. Here’s how I keep them straight:

  • a Factory creates and returns an object, but does not retain the created object for future use;

  • a Registry retains an object instance by name for repeated use, but does not create that object;

  • a Service Locator contains a named object (a “service”) and creates it if it
    has a definition for that service; the creation logic might itself be a Factory, and the retention logic might itself be a Registry.

By way of example, I have put together an example service locator implementation along with an embedded test case. It has only two methods: set() and get():

  • The set() method takes an object name as its first argument, and
    a factory (in this case, a callable). The factories are retained in a registry (in this case, a plain-old PHP array).

  • The get() method looks in a registry of instances for the requested object by name and returns it. If the object is not there, it uses the factory for that object name and retains it in the instance registry.

Here’s an example use:

<?php
// create the service locator
$locator = new PmjonesServiceLocator;

// add a factory; this can be any callable, whether
// a closure, an object with an __invoke() method,
// a function name, a static class method, an object
// instance and method name, etc.
$locator->set('my_object', function () {
    return new VendorPackageClassName;
});

// get back an object instance; the locator
// will create and retain it the first time
$one = $locator->get('my_object');

// the next and subsequent times, the locator
// return the same instance
$two = $locator->get('my_object');
var_dump($one === $two); // true
?>

That should help illustrate the difference between a factory, a registry, and a service locator.

Again, I must stress: Service locators can easily get out of control. If you can avoid using a service locator, you should. Instead of service locators, use dependency injection proper as much as possible, even though it is more work up front. If you must use a service locator: place only one type of object in it, use multiple locators if necessary, and inject each locator instead of using static calls for access.

Afterword

In Aura, we use dependency injection everywhere, all the time. In the rare cases that a service locator makes sense, that locator follows the “only one type of object” rule, and the locator itself is injected via the DI container. This makes the codebase very clean and a lot easier to test.

If you like clean code, fully decoupled libraries, and truly independent packages, then the Aura project is for you. Download a single package and start using it in your project today, with no added dependencies.

Quicker, Easier, More Seductive: Restraining Your Service Locators

tl;dr: Go ahead and use Service Locator, but make sure each Locator contains only one type of object. This condition of restraint will keep a single locator from becoming a menace.

Quicker, Easier, More Seductive

I mentioned in passing last week, “When it comes to Inversion of Control, a Service Locator is like the Dark Side of the Force: quicker, easier, more seductive. But it gets you into trouble later on. Go with Dependency Injection whenever you can instead.” I want to elaborate on that a bit.

The question is not “Which is better and which is worse, DI container or Service Locator?” The question is “What are the proper limits on DI containers and Service Locators?”

To establish my bona fides here, I’ll point out that I used Service Locator almost exclusively for several years. Solar::dependency() was Solar’s inversion-of-control system, and it was very useful for its purpose. But knowing what I know now, it is much more a Service Locator than a Dependency Injection container. (As a side note, universal constructor was a natural outgrowth of having a Service Locator try to take on Dependency-Injection-like behaviors.)

Service locators are a great boon, and very easy to set up and use. But they also present great problems as systems grow larger. Testing becomes more and more difficult. Dependencies are obscured. If the locator is used via static methods, it’s even harder, and more obscure.

Dependency injection containers take more up-front setup, and more up-front skill and discipline on the part of the developer. They are harder to understand, and it’s harder to get the concepts that go along with DI containers (factories in particular). I’m not kidding when I say it took me a year (!) of reading Misko Hevery and getting input from more experienced DI developers before I began to get it.

It is for that reason I say that Service Locator is “quicker, easier, more seductive.” It’s not that Service Locator is bad, it’s that it gets out of control very quickly.

Restraining Your Service Locators

Service Locator still has a useful purpose, but its scope must be limited to make sure it does not grow into a monster. Other people have suggested various rules on when to use a Service Locator, and when to use a Dependency Injection container. I will not add to those rules.

Instead, I will tell you to go ahead and inject a Service Locator (no static calls!) but on one condition:

A Service Locator should contain only one type of object.

When I say “type” I don’t mean “anything my controller might happen to need.” I mean, loosely speaking, “one class of object” or “objects with the same purpose”. For example, the Aura filter system uses a locator to make different validation and sanitizing rules available. The v1 view package uses a locator for the helper objects; we have gone so far as to extract the locator and helpers to their own package in v2. Note that these locators are highly specific and handle only one type of object.

  • What if you need multiple types of objects? Inject multiple types of Service Locators.

  • What if you only need one each of several different objects? Inject those objects directly, probably via constructor arguments, instead of retrieving them from a Service Locator.

  • What if some of the objects are optional, or only needed some of the time? Inject them via setter methods.

  • What if it ends up that you genuinely need to inject lots of different locators? Take it as a sign that you need to refactor the object that needs the many different locators. Consider wrapping those parts of the object into a service, inject the locators to the service, and inject the service to the original object.

  • If you find yourself injecting a Dependency Injection container into an object so that the object can retrieve dependencies from the container, you are using the Dependency Injection container as a Service Locator. The “only one type of object” rule applies in that case. The only way around it is to stop injecting the Dependency Injection container and instead inject a particular Service Locator, or to inject the specific needed objects.

The condition of “only one type of object” is imperfect, but it should begin set you on the correct path; that is, toward being able to identify dependencies. The goal is not immediate perfection, but gradual improvement. The idea is to purposely make Service Locator a little less seductive, to add constraints to it, so that it does not become a menace later.

Afterword

If you like clean code, fully decoupled libraries, and truly independent packages, then the Aura project is for you. Download a single package and start using it in your project today, with no added dependencies.