Loading...

Follow Matthias Noback Blog on Feedspot

Continue with Google
Continue with Facebook
or

Valid

"Can you make a query bus with SimpleBus?" The question has been asked many times. I've always said no. Basically, because I didn't build in the option to return anything from a command handler. So a handler could never become a query handler, since a query will of course have to return something.

I've always thought that the demand for a query bus is just a sign of the need for symmetry. If you have command and query methods, then why not have command and query buses too? A desire for symmetry isn't a bad thing per se. Symmetry is attractive because it feels natural, and that feeling can serve as design feedback. For instance, you can use lack of symmetry to find out what aspect of a design is still missing, or to find alternative solutions.

Nonetheless, I think that we may actually not need a query bus at all.

The return type of a query bus is "mixed"

A command or query bus interface will look something like this:

interface Bus
{
    /**
     * @return mixed
     */
    public function handle(object $message);
}

A sample query and query handler would look like this:

final class GetExchangeRate
{
    // ...
}

final class GetExchangeRateHandler
{
    public function handle(GetExchangeRate $query): ExchangeRate
    {
        // ...
    }
}

When you pass an instance of GetExchangeRate to Bus::handle() it will eventually call GetExchangeRateHandler::handle() and return the value. But Bus::handle() has an unknown return type, which we would call "mixed". Now, you know that the return type is going to be ExchangeRate, but a compiler wouldn't know. Nor does your IDE.

// What type of value is `$result`?
$result = $bus->handle(new GetExchangeRate(/* ... */));

This situation reminds me of the problem of a service locator (or container, used as locator) that offers a generic method for retrieving services:

interface Container
{
    public function get(string $id): object;
}

You don't know what you're going to get until you get it. Still, you rely on it to return the thing you were expecting to get.

Implicit dependencies

This brings me to the next objection: if you know which service is going to answer your query, and what type the answer is going to be, why would you depend on another service?

If I see a service that needs an exchange rate, I would expect this service to have a dependency called ExchangeRateRepository, or ExchangeRateProvider, or anything else, but not a QueryBus, or even a Bus. I like to see what the actual dependencies of a service are.

final class CreateInvoice
{
    // What does this service need a `Bus` for?!

    public function __construct(Bus $bus)
    {
        // ...
    }
}

In fact, this argument is also valid for the command bus itself; we may not even need it, since there is one command handler for a given command. Why not call the handler directly? For the automatic database transaction wrapping the handler? I actually prefer dealing with the transaction in the repository implementation only. Automatic event dispatching? I do that manually in my application service.

Really, the main thing that I hope the command bus brought us, is a tendency to model use cases as application services, which are independent of an application's infrastructure. And I introduced the void return type for command handlers to prevent write model entities from ending up in the views. However, I've become much less dogmatic over the years: I happily return IDs of new entities from my application services these days.

No need for middleware

Actually, the idea of the command bus having middleware that could do things before or after executing the command handler, was pretty neat. Dealing with database transactions, dispatching events, logging, security checks, etc. However, middlewares also tend to hide important facts from the casual reader. One type of middleware is quite powerful nonetheless: one that serializes an incoming message and adds it to a queue for asynchronous processing. This works particularly well with commands, because they don't return anything anyway.

I'm not sure if any of these middleware solutions will be interesting for a query bus though. Queries shouldn't need to run within a database transaction. They won't dispatch events, they won't need logging, etc. In particular, they shouldn't need to be queued. That would not make a timely answer to your query likely.

A query handler that doesn't need middlewares, doesn't need a bus either. The only thing the bus can still do in that case is directly forward the query to the right handler. And, as I mentioned, if there's just one handler, and you wrote it, why not make it an explicit dependency and call it directly?

Suggested refactoring: Replace Query Bus with Service Dependency

It won't be a surprise that my advice is to replace usages of a query bus with a real service dependency. This gives you the following benefits:

  • Service dependencies will be explicit
  • Return types will be specific

The refactoring in case of the GetExchangeRate case looks as follows:

// Before:

final class GetExchangeRate
{
    public function __construct(Currency $from, Currency $to, Date $date)
    {
         // ...
    }
}

final class GetExchangeRateHandler
{
    public function handle(GetExchangeRate $query): ExchangeRate
    {
        // ...
    }
}

 // After:

final class ExchangeRateProvider
{
    public function getExchangeRateFor(Currency $from, Currency $to, Date $date): ExchangeRate
    {
        // ...
    }
}

Also, every service that used to depend on the bus for answering their GetExchangeRate query, would now depend on ExchangeRateProvider and it should get this dependency injected as a constructor argument.

final class CreateInvoice
{
    public function __construct(ExchangeRateProvider $exchangeRateProvider)
    {
        // ...
    }
}
Optional refactoring: Introduce Parameter Object

As you may have noticed, the constructor arguments of the query object are now the method arguments of getExchangeRateFor(). This means that we've applied the opposite of the Introduce Parameter Object refactoring. I find that in some cases it still pays to keep the query object. In particular if it's something that represents a complicated query, with multiple options for filtering, searching, limiting, etc. In that case, sometimes a builder can give quite elegant results:

final class Invoices
{
    public static function all(): self
    {
        // ...
    }

    public function createdAfter(Date $date): self
    {
        // ...
    }

    public function thatHaveBeenPaid(): self
    {
        // ...
    }
}

$this->invoiceRepository->find(
    Invoices::all()
        ->createdAfter(Date::fromString('2019-06-30'),
        ->thatHaveBeenPaid()
);
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

With excerpts from the documentation

I have worked with symfony 1, Symfony 2+, Zend Framework 1, Zend Expressive, but never with Laravel. Until now. My partner was looking for a way to learn PHP and building web applications with it. Most of my own framework knowledge is related to Symfony, so my initial plan was to find a Symfony course for her. However, Jeffrey Way's Laracasts also came to mind, and I thought it would be an interesting learning experience for us both if Laravel would be the framework of choice in this matter. It turned out to be a good idea. My partner is making good progress, and I get to see what Laravel is all about. We have a lot of fun together, finding out how it works, or what you're supposed to be doing with it.

As a side-project, I've been reading the official Laravel documentation. Being a human with framework habits, I couldn't help but compare the Laravel approach to the Symfony approach. I've also compared some of the suggestions from the documentation with what I think are best practices for any web application, regardless the framework that you choose. Something to keep in mind when reading this article is that my approach to web application architecture is to keep the framework and other infrastructural concerns far from the code that represent my application's use cases and the domain models it contains. See also the series I wrote about this approach earlier (part 1, part 2 and part 3). I'm usually not looking for a way to develop something as quick as possible, or make development as convenient as possible. I try to find ways to protect its future against external influences, like changes in the language, the framework, a desire to switch to a different database, queueing system, etc. This will certainly color some of my observations in this and following articles, including the advice I give here on which feature to use, and which ones to ignore.

I also want to make clear that I'm absolutely not here to bash Laravel or anything. Quite the contrary actually, I think its builders have made some great decisions. They have also added things that I guess might nudge people in the wrong direction in terms of object design, but keep in mind: this is all my opinion. Of course, I try to give proper arguments, but in the end I'm not here to say: use Symfony, ditch Laravel. If anything, my message when it comes to frameworks would be: use them to your advantage, but only in the parts of your code base where it makes sense. Don't let them determine your overall design (or architecture), make sure you can swap parts of them out when you want or need to. If Laravel/Symfony/Zend/etc. suits your needs today, use it today, but prepare for the day when you don't to use it anymore.

With all disclaimers out of the way, let's go!

The service container

Let's start with a discussion about the service container. After I wrote the article on Hand-written service containers someone pointed out to me that my solution wasn't the only one that has the advantage of being easy to refactor; the classes, interfaces, and methods that you use in Laravel service definitions are also indexable by your IDE and are therefore easy to rename, move, etc. That's totally correct. This is what a service definition looks like:

// Defining a service inside a closure:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

// Using it somewhere else:

$this->app->make('Helpspot\Api');

// Note: you can use `Api::class` instead of spelling out the full class name

However, most services don't even need all this work, because you can give the container instructions. E.g. instead of binding an interface with a closure, you can also bind it with a class name, so that, whenever an object requires an instance of the interface, it will get an instance of that class injected:

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);
Shared versus singleton services

I think it's very interesting that by default the services that you define using bind() are not shared, that is, the next time we'd call $this->app->make(), you will get a fresh instance. For Symfony, the default setting is the opposite: when you define a service, it's going to be shared. I definitely prefer the Laravel approach. For two reasons: first, services aren't objects that need reference equality; you shouldn't be worried about the exact instance of a service that you retrieve. You should only care about the fact that it is an instance of the requested class or interface. Second, creating a fresh copy for a service will prevent you from making the service stateful. Keeping state inside the service doesn't make sense, since you can't rely on that state to be there the next time you request that service. (Actually, I'm not sure if I'm projecting my ideals onto the Laravel service container now; maybe not having "singleton" services by default doesn't keep people from writing stateful services, I don't know.)

By the way, it's really cool how the first example of dependency injection in this documentation section is a UserController which needs a UserRepository. This is quite surprising to me, since for a very long time, Symfony developers have been debating whether or not to use constructor injection for controllers. I've always liked the idea, because I've always followed the rule that services should have all their dependencies injected as constructor arguments. However, with Symfony this has been problematic and the established practice has just been to grab services from the service container directly, whenever needed (but only in the controller; more about that later).

Anyway, what I think is a good rule for services is that only the ones that represent a shared resource (like a database connection) should be "singleton" services, and that all the others should be created whenever they are needed, without reusing existing instances. By the way, "singleton" service is a peculiar name here. I like it because it conveys the spirit that there is supposed to be only one instance, like with the Singleton design pattern, but since it does not require the class to implement that pattern, it's a bit confusing too. I just hope that you don't use language constructs to make a class by definition a singleton, but that you'll rely on the service container to make sure only one instance gets created.

Something else to mention in the context of "binding": if a service needs a configuration value to be provided as a string (or some other primitive-type value), you can bind this value too:

$this->app->when('HelpSpot\API')
          ->needs('$apiKey')
          ->give('secret');

This way, the service container will be able to construct the object, providing 'secret' as the constructor argument for a parameter called $apiKey. Symfony has this same option for helping the service container find the right value for a primitive-type parameter. I don't like it. One problem for me is the fact that something that was previously internal to the class - the name of a constructor parameter - is now also used in some other place. This means that where it was previously completely safe to rename the parameter, now you have to also update the service bindings. A solution for this would be to define dedicated value objects for each configuration value and make them available as "services" too:

namespace HelpSpot;

/*
 * This class can be used to create an object which represents
 * the API key previously passed to the API services as a string:
 */
final class HelpSpotApiKey
{
    private $apiKey;

    private function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }

    public static function fromString(string $apiKey): self
    {
         return new self($apiKey);
    }

    public function asString(): string
    {
        return $this->apiKey;
    }
}

final class API
{
    /*
     * We no longer rely on a string, but on a `HelpSpotApiKey` 
     * value object:
     */
    public function __construct(HelpSpotApiKey $apiKey)
    {
        // ...

        // to use the API key:
        $apiKey->asString();
    }
}

/*
 * As soon as we bind the `HelpSpotApiKey` value object, Laravel will
 * be able to instantiate the `API` service all by itself:
 */ 
$this->app->bind(HelpSpotApiKey::class, function ($app) {
    return HelpSpotApiKey::fromString('secret');
});

Note: if you feel like writing classes like HelpSpotApiKey is a lot of work; I completely agree. If you want, create a little trait that saves you from writing all that code by hand. The added benefit to me is definitely worth the exta classes.

There are some other service container configuration options that seem quite useful, e.g. the ability to bind an interface to different classes, based on which service requires it (Contextual binding), and another one that is very powerful - the ability to tag services and retrieve all services with a given tag. Symfony has this too, and I've always found it an amazing feature. The major difference here is that, instead of dealing with services during the container compilation phase (as is done in Symfony), with Laravel you can just grab the tagged services. I remember dealing with tagged services in Symfony for the first time was a real mind-bender. With Laravel it is more intuitive, for sure:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});
Instantiating services

The problems for me start with the ability to use the bootstrapped application to resolve services using its make() method:

$api = $this->app->make('HelpSpot\API');

This is a power that belongs not to the user, but to the framework. The only place where make() should have to be called is inside the framework kernel, after it has found out which controller it should call, based on the available HTTP request information (e.g. the route, its parameters, the host name, etc.). At that point, the kernel will instantiate the controller. The controller will have all its dependencies injected as controller arguments, and any of those services will have its dependencies injected as constructor arguments too. That way, calling the controller will indirectly produce a number of calls to the service's dependencies, but along the way, no service will have to resolve more dependencies on-the-spot.

Dynamically resolving dependencies has always been considered a design problem anyway, for many reasons. It hides the actual dependencies, making it difficult to get a clear picture of what those dependencies are, and consequently, of what the job of a service is. Being able to look at a constructor and see what its dependencies are is a great way of getting to know a service. I wouldn't want to read the code in detail to find out what a service does. The public API of a service (the methods, including the constructor, their parameter types, and their return values) should tell me all I need to know.

For this reason, I'm not particularly happy about the suggestion in the documentation that "If you are in a location of your code that does not have access to the $app variable, you may use the global resolve helper":

$api = resolve('HelpSpot\API');
Static dependency instantiation

There's another problem with using static functions for resolving services (and some framework use them for resolving configuration too). They introduce framework coupling. It's very likely that in just one or two years, things will be completely different, and we no longer should (or can't) use this resolve() function anymore. Maybe we want to switch to a different framework, maybe the team agrees that they don't want to resolve dependencies on the spot. Then you're stuck with them.

I've recently had this experience with Zend Framework 1's Zend_Registry which is used to share services and configuration across a code base. Although Zend_Registry if more like a globally available map of things, and by far not an auto-wiring service container, it's still the same pattern. Application code will have lots of calls like Zend_Registry::get('Zend_Translate') and the likes, to retrieve globally available services. When using resolve(), or even $this->app->make(), you'll end up with the problem that you're not only depending on a service, you're now also depending on the service you need for retrieving that service. And this is a really painful thing. As an example of how this becomes very impractical: if your application uses Zend_Registry::get('Zend_Translate') to retrieve the translator service, and you now want to use the Symfony service container in your project, you'll either have to do something sneaky with Zend_Registry to make that work (but then you'll still depend on Zend_Registry, which you wanted to get rid of), or you'll have to rewrite all the code to get the translator injected, which will be a lot of work. In legacy migration projects, the first solution will be chosen, but this is far from ideal, and eventually you'll have to rewrite everything to use proper constructor dependency injection anyway.

Rewriting to constructor injection is mainly a problem if the instantiation of a class is not completely under your control. E.g. when the framework instantiates your controller (like Symfony does, if it isn't defined as a service). It will just be new $controllerClass(), no arguments provided. In such cases, instead of giving up the possibility to inject dependencies, try to get control over the instantiation of your classes again, so you can also use any of the dependency injection benefits that your framework has to offer.

So, this is why my advice is: don't use any of these seemingly convenient shortcuts. Just use constructor injection always. Anyway, why is constructor injection considered to be so very painful that Laravel provides us shortcuts for it? It already has something very cool that makes manualy make()-ing services obsolete: Automatic Injection.

Automatic dependency injection

As you may know, the difference between a framework and a library is: you call a library, but the framework calls you. At the crucial moment that it does this, e.g. when it calls your controller, the Laravel service container will set up your controller by automatically injecting all the dependencies that it needs. As I mentioned earlier, this is how I think it should be. It also means that, really, there is no need for you to call the service container yourself, to resolve dependencies for you. You only have to make sure that they are already injected as constructor arguments.

Something I find weird is the possibility to injection dependencies as regular method arguments. The documentation mentions the handle() method of queued jobs. It looks like a job is a class which combines both the payload and the logic for processing that payload in a single class. This is where the need to inject dependencies as method arguments comes from. This need would completely disappear if you would simply separate these two things, i.e. have a class for the payload, and another class to process that payload. The first class will be a simple data transfer object, with no behavior at all, because it will just serve as a means for passing the data from the backend application to the queue worker. The second class will be a proper service, with its dependencies injected as constructor arguments. In my book (literally ;)), you can't combine a data holder and a service object in one class. But it's a matter of style after all.

Manipulating services after they have been instantiated

Using something called container events the service container makes it possible for you to manipulate a service after it has been instantiated:

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

The documentation mentions: "As you can see, the object being resolved will be passed to the callback, allowing you to set any additional properties on the object before it is given to its consumer."

Implicitly, this means that services can be designed to be mutable. A service, once instantiated, can be reconfigured and to start behaving differently. This is a design style I don't recommend, since the service that gets injected into other services, is no longer a predictable thing. Any client that has access to it can change its behavior. Of course, you could say: that own't happen. But it happens, and leads to very confusing problems. I think it's a great idea to make services immutable; provide every dependency they need as a constructor argument, and it should be possible for it to run like machine, forever.

I find that usually the need to inject things into a service after construction time is caused be two issues that can easily be solved.

  1. It may seem more convenient to call setters multiple times, than to prepare a data structure for the constructor. In that case: just take that tiny bit of extra time.
  2. Data ends up being set on the service that is not strictly a dependency or a configuration value, but is in fact contextual data, and therefore can't be resolved by the container all by itself. For example, maybe you want to inject into the service the current user's IP address, or the request URI. Or maybe even the entire session object. This kind of data should not be injected, but passed to it as a regular method argument. I've described this in more detail in another article about Context passing.
Conclusion

Laravel's service container looks great. I like the idea that it can figure things out mostly by itself. I like that it's PHP-based, and that its syntax is quite compact. I think that most of the convenience functions (e.g. resolve()) and exotic options (like $this->app->resolving()) should be ignored. The best thing you can do for your application in the long term is to let all your services use dependency injection, and to inject only constructor arguments. This keeps things simple, but also portable to other frameworks with other dependency injection containers, or other architectural styles, when the time is there.

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Matthias Noback Blog by Matthias Noback - 2M ago

This week I attended and spoke at the Newcrafts conference in Paris. The following is a mix of notes and personal observations I wanted to share, centered around some of the talks I saw there.

Romeu Romera: Bourdieu's Social theory and our work in tech

I had never attended a talk by Romeu before. I really enjoyed this one. Somehow I already knew that he uses a mindmap to support his talk. I thought he would use an existing mind map to navigate through the talk, but it turned out he was creating one during the talk. For me personally, a slide deck helps to keep track of the story, and it helps me remember all the different topics I need to talk about. Not so much for Romeu, who knew exactly what he was going to talk about, and didn't seem to forget to mention any important part, or make important connections.

The topic is one that seems close to his heart. Still, he called himself "not an expert", saying that this talk was an experiment. It turned out that he was hinting at the fact that the subject matter is vast, and he could only cover some parts of it during the talk. Still, the things he covered, maybe simplified a lot, were very impactful, and very interesting. I'd definitely recommend watching this talk once it becomes available online.

More than with any other talk, I think you can't help it but apply the ideas mentioned to your own situation when you listen to Romeu. He covered three parts of Bourdieu's social theory. The first part is about icons of power. The way you look and behave shows how much power you have. This modified appearance is called Symbolic Violence; an act of violance people in positions of power put onto themselves. I think in the context of conferences, being a public speaker is a great example of violence the speaker puts onto themselves. Personally, I often find it a painful experience (although I'll keep doing it as long as there's a way to help people do a better job in any way).

The second part of the theory has to do with Cultural Capital. Everyone has their own amount of cultural capital. Take for example the people in your team. Some will have more experience than others, a deeper understanding of design, architecture, etc. People with less cultural capital will be seen as lesser people. Having more cultural capital can also be an issue with speakers at a conference, where they will be automatically taken to be experts, to be better humans (or at least, better designers, programmers, etc.). They will be perceived to be more powerful, and more right. This isn't fair to either party; speakers, and attendees alike, but it's how the game gets played.

Differences in the amounts of cultural capital between people will result in Dissociation. The first thing that might happen is that you see a person with less cultural capital as someone you can ignore, not take seriously, etc. The other thing that could happen is that you'll feel that a person with more cultural capital than you is unreachable, and that they wouldn't be interested in even talking to you. Personally I can relate to this problem a lot. When I'm at a conference, it totally depends how I feel: if I feel like I have a sufficient amount of cultural capital, I'll be perfectly fine, and can speak freely with anyone in the room. If I feel that I lack cultural capital, I'm very shy, and generally tend to avoid other speakers, as I will quickly feel like an imposter, noticing a mismatch between the expected and the actual amount of cultural capital.

The third part of the theory is about Hexis, which means something like to what level you feel like you belong somewhere. Hexis could be considered "high" if you never doubt that you should be where you are now. It's low if you have doubts about your presence. Being self-condident is much appreciated, showing doubt is a signal of fragility, and it will look punishable. The immediate association I had, was how code reviews show a difference in seniority (which comes with self-confidence, never a doubt that you're in the right place). The senior developer is likely to provide a lot of nitpicking comments to the one who is more junior. The junior developer will likely have a hard time providing feedback to the senior. The situation gets worse if the senior is considered to be the boss/manager/team lead as well.

And this is where Romeu brings the discussion back to software development. The problem with some agile practices is that they assume equality in the workplace. Pair programming is easy if none of the programmers are the (perceived) boss. Retrospectives are easy if the (perceived) boss isn't there.

If you have enough cultural capital, and symbolic violence, you can ignore the problem. But if you have not, you can't. The problem is real. And of course, it's better if nobody would ignore the problem. It's also a good idea if the one with all the symbolic violence, in this case the speaker himself, doesn't provide a solution, because that solution would be made from a power position. Instead, we should all work together, so we can all feel like we belong here, like we're all equally important, like everybody can bring in their ideas.

Alberto Brandolini - The Gordian Knot

Alberto is always a joy to listen to. I found this talk very well-structured. He started out with some words about the book "Accellerate", which gives scientific support for certain practices we already considered to be best practices in our industry. One of the things so-called high-performing teams have is a short time between a commit and the release of that commit to production.

Being able to release code quickly to production requires a good culture, and that will be one of DevOps, one where there's a focus on quality, testing, and autonomy. What is culture? Alberto defines it as the accumulation of winning behavior in a group. And what is the winning behavior has a lot to do with the design of the system with which this group works. Alberto thinks it's very annoying how people ascribe a lack of quality, discipline, etc. to a mentality issue, or a culture issue that simply can't be fixed. It does seem like there are bigger issues, that can be fixed.

Daniel Pink's ideas about what motivates lead Alberto to introduce the "Pink Test" for software development teams. To be motivated, developers will need:

  1. Autonomy
  2. Mastery
  3. Purpose

After showing several examples of teams that score different results on the Pink Test, Alberto concludes that a Bounded Context (a Domain-Driven Design concept), can help you score maximally on the test, because it will be an autonomy context; one where you and your team can work mostly on your own. You can show and practice mastery within that context, and you'll be fully responsible for only this part. You can't move responsibility to another team, or other people. So, you'll be motivated to make it work well.

In order to feel purpose in your work, you still need an extra ingredient: feedback. You need to know how to get feedback, when to expect feedback. Will you hear positive feedback, or only negative feedback? Do you hear it from users, or from managers? Seeing how your software makes its users happy, is very important for a sense of purpose.

Not receiving positive feedback, can easily lead to fear of changing things, because if you break something, you'll definitely get that negative feedback. If you fix it, it will be assumed to be normal. For me personally, many, many experiences come to mind where this was the case. In fact, even if teams have celebrate-with-cake moments, they aren't really satisfying, because they usually celebrate a milestone, in terms of finished work, but not in terms of appreciated work. In my experience, celebrations don't involve users, nor focus on how the users enjoy the work that was just finished. The people in the celebration meeting may even be quietly angry about how much money it has cost them, how much still doesn't work, or how their managers are already pushing for the next thing that needs to be done.

Fear of negative feedback, but also fear of not being allowed to do something that you and your team think is necessary for your project, leads to lack of motivation. If you take a look at the Pink Test again, having to ask permission for testing means you're not very autonomous. Being dependent means that you're likely not able to perform your work in a masterful way. It robs you of purpose as well, since it's not your own boat you're sailing, it's someone else's.

Alberto gets back once more to the idea of a bounded context, and besides its function in domain modelling, points out how it's going to be really helpful in establishing a place where people can feel responsible, and experience pride in their work.

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

With a foreword by Ross Tuck

Today I've released the revised edition of my latest book "Style Guide for Object Design". Over the past few weeks I've fixed many issues, smaller and larger ones. Ross Tuck and Laura Cody have read the manuscript and provided me with excellent feedback, which has helped me a lot to get the book in a better shape. I added many sections, asides, and even an extra chapter, because some topics deserve more detail and background than just a few paragraphs. Oh, and Ross wrote the kindest of forewords to kick off the book. It's available in the free sample of the book.

The book will be available at the initial "preview release" price of 20 dollars (with a suggested price of 29 dollars) but only until April 15th. Use this link to apply the discount: https://leanpub.com/object-design/c/PREVIEW_RELEASE.

What happens on April 15th?

Over the past few weeks I've been talking with some fine people from Manning, publisher of tech books. This resulted in a contract with them. They will republish the "Style Guide for Object Design". There will be an e-book, but also a printed version, which is great.

The book currently has code samples in PHP and contains some PHP-specific suggestions, but the Manning edition will have Java code samples, and no programming language or ecosystem-specific advice, hopefully making the book useful and appealing to a larger group of object-oriented programmers. This is an exciting thing for me as a book author, because I may be able to reach more people in this way.

I know that some readers prefer to read the PHP version, so that's why the version as it is now will be available until April 15th. From that moment on, you won't be able to buy it anymore. However, if you've bought the e-book from Leanpub, you will be granted access to the Manning Early Access Program (MEAP) for the book, meaning that you will eventually be able to read the Manning/Java edition too. Once this is possible, I'll send out an email to existing readers, and update the Leanpub page with instructions for joining the program.

Changelog

If you already read (part of) the book, here's a summary of the most important changes, so you can decide if it'll be useful to download and read the new version. The full change log is in the back of the book and it includes linkes to the chapters and sections that have been updated.

  • Foreword

    • Added the foreword by Ross Tuck.
  • The lifecycle of an object

    • Rewrote the explanation about the two types of objects. What really defines these types is how they are related to each other. The first type uses the second type (services versus materials).
  • Creating services

    • Added a subsection: "Keeping together configuration values that belong together", which introduces a way to keep together configuration values that belong together.
    • Added an aside: "What if I need the service and the service I retrieve from it?", which answers a common question that was asked by the technical reviewer.
  • Creating other objects

    • Added a new section: "Don't use custom exception classes for invalid argument exceptions", explaining why you don't usually need custom exception classes for invalid argument exceptions.
    • Added an aside: "Adding more object types also leads to more typing, is that really necessary?", explaining some of the benefits of using object types instead of primitive types, just in case people are wondering if all that extra typing is really necessary.
    • Added another example to the section "Don't inject dependencies, optionally pass them as method arguments", explaining how you could rewrite the currency conversion logic using a simple services. Added a comment about the design trade-offs you have to make in this type of situation.
    • Added an aside about PHP's class-based scoping, explaining how it's possible that a named constructor can manipulate private properties directly.
    • Added a subsection "Optionally use the private constructor to enforce constraints" with an example showing how you can use the private constructor when you have multiple named constructors.
    • Finish the chapter with a new section: "The exception to the rule: Data transfer objects" about Data transfer objects, a type of object with less strict rules, which was not yet discussed in detail.
  • Manipulating objects

    • Added a new introduction, explaining the different types of "other" objects and what their design characteristics are in the area of object manipulation, covering Entities, Value objects and DTOs.
    • Add an aside: "A third-party library has some object design issues, what do I do?"
  • Retrieving information

    • Added an aside: "How do you handle ambiguous naming?", explaining how you can deal with query method names that could be read as verbs (e.g. "name").
  • Dividing responsibilities

    • Added this new chapter, which elaborates on how you can make some objects be responsible for changing state, and others for providing information. It has a new, more detailed example, which is also more realistic than the current one about a player and its position.
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

You say "convention over configuration;" I hear "ambient information stuck in someone's head." You say "configuration over hardcoding;" I hear "information in a different language that must be parsed, can be malformed, or not exist."

— Paul Snively (@paul_snively) March 2, 2019

Dependency injection is very important. Dependency injection containers are too. The trouble is with the tools, that let us define services in a meta-language, and rely on conventions to work well. This extra layer requires the "ambient information" Paul speaks about in his tweet, and easily lets us make mistakes that we wouldn't make if we'd just write out the code for instantiating our services.

Please consider this article to be a thought experiment. If its conclusions are convincing to you, decide for yourself if you want to make it a coding experiment as well.

The alternative: a hand-written service container

I've been using hand-written service containers for workshop projects, and it turns out that it's very nice to work with them. A hand-written service container would look like this:

final class ServiceContainer
{
    public function finalizeInvoiceController(): FinalizeInvoiceController
    {
        return new FinalizeInvoiceController(
            new InvoiceService(
                new InvoiceRepository(
                    $this->dbConnection()
                )
            )
        );
    }

    private function dbConnection(): Connection
    {
        static $connection;

        return $connection ?: $connection = new Connection(/* ... */);
    }
}

The router/dispatcher/controller listener, or any kind of middleware you have for processing an incoming web request, could retrieve a controller from the service container, and call its main method. Simplified, the code would look this:

$serviceContainer = new ServiceContainer();

if ($request->getUri() === '/finalize-invoice') {
    return $serviceContainer->finalizeInvoiceController()->__invoke($request);
}
// and so on

We see the power of dependency injection here: the service won't have to fetch its dependencies, it will get them injected. The controller here is a so-called "entry point" for the service container, because it's a public service that can be requested from it. All the dependencies of an entry-point service (and the dependencies of its dependencies, and so on), will be private services, which can't be fetched directly from the container.

There are many things that I like about a hand-written dependency injection container. Every one of these advantages can show how many modern service containers have to reinvent features that you already have in the programming language itself.

No service ID naming conventions

For starters, service containers usually allow you to request services using a method like get(string $id). The hand-written container doesn't have such a generic service getter. This means, you don't have to think about what the ID should be of every service you want to define. You don't have to come up with arbitrary naming conventions, and you don't have to deal with inconsistent naming schemes in a legacy single project.

The name of a service is just the name of its factory method. Choosing a service name is therefore the same as choosing a method name. But since every method in your service container is going to create and return an object of a given type, why not use that type's name as the name of the method? In fact, this is what most service containers have also started doing at some point: they recommend using the name of the class you want to instantiate.

Type-safe, with full support for static analysis

Several years ago I was looking for a way to check the quality of the Symfony service definitions that I wrote in Yaml. So I created a tool for validating service definitions created with the Symfony Dependency Injection Component. It would inspect the service definitions and find out if they had the right number constructor arguments, if the class name it referenced actually existed, etc. This tool helped me catch several issues that I would only have been able to find out by clicking through the entire web application.

Instead of doing complicated and incomplete analysis after writing service definitions in Yaml (or any other meta-language for that matter), if I write them in PHP, I get all the support from static analysis tools. Even if I don't use a separate tool like PHPStan or Psalm, PhpStorm will point out any issues early on. Missing classes, missing import statements, too few or too many arguments, everything will be pointed out to me when I'm editing the ServiceContainer class in my IDE. This is a huge advantage.

Easy to refactor

Because analysis is easy, we can also expect all the help there is when refactoring our code. If we change production code, opening the ServiceContainer in your IDE will show you any issues you've produced. Furthermore, because there's no special service definition format, your IDE doesn't need a special extension to deal with it. It's just plain PHP code. So any refactoring tool that you use (e.g. rename method, move to different namespace, etc.) will also deal with any existing usages inside the ServiceContainer class.

Easy to make a distinction between public entry points and private dependencies

I like how the Symfony service container allows users to make a distinction between private and public services. Public ones can be fetched using a call to get($id), private ones can only be used as dependencies for other (public or private) services. Some services indeed deserve to be public (mostly the services we earlier called "entry points"), most should remain private. Of course, the distinction between public and private services reminds us of the way we can have public and private methods too, and in fact, if you write your own service container, you will use these method scopes to accomplish the same thing.

If you hand-write the service container you can do some optimizations too, just like the Symfony container does them. For instance, if you have a private service that's only used in one place, you can inline its instantiation. As an example, consider the private invoiceService() method:

public function finalizeInvoiceController(): FinalizeInvoiceController
{
    return new FinalizeInvoiceController(
        $this->invoiceService()
    );
}

private function invoiceService(): InvoiceService()
{
    return new InvoiceService(
        new InvoiceRepository(
            $this->dbConnection()
        )
    );
}

This method is only used by finalizeInvoiceController(), so we can safely inline it:

public function finalizeInvoiceController(): FinalizeInvoiceController
{
    return new FinalizeInvoiceController(
        new InvoiceService(
            new InvoiceRepository(
                $this->dbConnection()
            )
        )
    );
}

If, due to refactoring efforts, a private service is no longer needed, PhpStorm will tell you about it.

No need to define partial service definitions to assist auto-wiring

Auto-wiring has become quite popular, but I'm not convinced it's the way to go. I'm sure most scenarios have been covered by now, so I'm not afraid that things won't work out between me and auto-wiring. However, we'll always have to do tricks to make it work. We have to give the wirer hints about which implementations to use. This means that we may be able to delete many service definitions, but we also have to keep some around, since there are some things that won't work without them. You need to have in-depth knowledge about the dependency injection tool you use, and you need to learn the syntax for helping the auto-wirer. Worse, you may decide to adopt your production code so that the wirer can understand it.

Needless to say: if you write your service definitions in your own PHP class you'll never need custom syntax. In fact, you don't need to look up specific documentation at all, because you don't have to worry about failures to resolve dependencies; you make all the decisions yourself when you write your ServiceContainer class.

No need to inject primitive-type values by their parameter name

A downside of auto-wiring containers is that they need special instructions when the injected constructor arguments aren't objects, but primitive-type configuration values.

namespace App\Db;

final class Connection
{
    private $dsn;

    public function __construct(string $dsn)
    {
        // ...

        $this->dsn = $dsn;    
    }
}

Object-type constructor arguments can usually be resolved, but the service definition needs an extra hint for the $dsn argument:

    App\Db\Connection:
        arguments:
            $dsn: 'mysql:host=localhost;dbname=testdb'

This exposes an implementation aspect of the service class itself, which would normally remain hidden behind its public interface. To a client that instantiates an instance of Connection, only the parameter types should be relevant, not their names. In fact, a developer should be able to rename the parameter $dsn to something else, without breaking the application. Of course, a smart container builder will warn you about it when you rename a parameter, but this comes with some extra indirection that wouldn't be needed at all if we'd just write the instantiation logic inside a manual ServiceContainer class, where parameter names are irrelevant (as they should be).

No magic effects on the service container

Talking about auto-wiring, I have to say I dislike the fact that the location of a file containing a class has an influence on it being defined as a service in the container. I'd want to be able to create a new class anywhere in the project, and decide for myself whether or not it gets defined as a service. Needless to say, you won't have any magical effects like this if you write a custom service container, but personally I won't miss them.

Easier to distinguish between services and singletons

Most service containers will automatically share instances of a service; if a service has been instantiated once, the next time you ask for it, you'll get the exact same object instance. This is important for things like a database connection; you want to reuse the connection, instead of connecting to the database every time you need something from it. However, most services should be stateless anyway, and in that case it isn't really necessary to retrieve the exact same instance.

A service that's instantiated once and then shared between different clients is traditionally called a "singleton" service. Singleton services were usually implemented using the Singleton design pattern, which actually protects them from being instantiated more than once. Nowadays a service container manages service instances, and although it doesn't use the Singleton design pattern, it still makes every service effectively a singleton service: there's at most one instance of every service.

What I like about using a hand-written service container is that you can make a clear distinction between services for which it's actually important to have only one instance, and services for which it doesn't matter. Using the same example as earlier, note that the controller service can be re-instantiated every time a client needs it, and the connection service will be stored in a static variable, so that it can be reused:

final class ServiceContainer
{
    public function finalizeInvoiceController(): FinalizeInvoiceController
    {
        return new FinalizeInvoiceController(/* ... */);
    }

    private function dbConnection(): Connection
    {
        static $connection;

        return $connection ?: $connection = new Connection(/* ... */);
    }
} 

What about performance? Well, if it starts to hurt, you can always add some more shared services. But in most cases, I don't think it'll be needed. In part because of the fire-and-forget nature of the PHP server, but also because most services will be instantiated and used only once or twice anyway.

Easier to override parts for testing

With a service container based on a meta-language, like Yaml service definitions, you have to build in a mechanism for modifying the service definitions for use in different environments. With Yaml you can load in multiple service definition files and override service definitions and parameters. But just like "public" and "private" services, the concept of overriding services is also built into the programming language itself, namely by overriding methods. For example, if you want to use a fake/catch-all mailer or something while in development, you can do something like this:

abstract class ServiceContainer
{
    abstract protected function mailer(): Mailer;
}

final class DevelopmentServiceContainer extends ServiceContainer
{
    protected function mailer(): Mailer
    {
        return new FakeMailer();
    }
}

final class ProductionServiceContainer extends ServiceContainer
{
    protected function mailer(): Mailer
    {
        return new SmtpMailer(/* ... */);
    }
}
Optionally testable

If you want, you can even write an integration test for your hand-written service container. Then you could prove that the DevelopmentServiceContainer actually uses a FakeMailer. Or you can verify that all the public entry-points can be instantiated without producing any runtime errors (although static analysis will catch most of the issues already).

To be honest, this is also possible when you use a meta-language for defining services; you can always run tests against the compiled service container. However, I don't see this happening often, so I just wanted to mention the possibility here.

Composing containers? Separating containers?

A big issue with maintaining a hand-written container is that you wouldn't want to rewrite all of the framework's service definitions yourself. In fact, this isn't what I'm suggesting here. If you use a framework which ships with a service container, you just don't have to use it for your own service definitions. For instance, you can define your service container as a service in your framework's service container, and have access to it in the usual ways the framework supports.

You can even have multiple containers, for each of the modules/contexts you distinguish in your application. This could help keeping them actually separated (however, there's no technical way to enforce a context to protect the integrity of a context, it'll always be a people issue too).

Note that composition of containers is something that service containers don't usually offer, but the programming language itself is capable of. You only have to inject containers as constructor arguments of other containers.

Conclusion

In an educational setting I found that one of the biggest advantages of having your own hand-written service container for your Application and Domain layer services is that it allows you to write acceptance tests using these services. You can freely instantiate the container in your Behat FeatureContext. You can then write tests which talk to the Application layer (instead of the Infrastructure layer as they usually do). These tests will run in a very short time, but most importantly: they will be less brittle, because they don't (and can't) rely on all kinds of infrastructure-level peculiarities.

In a project setting, I haven't been fortunate enough to be able to use a hand-written service container. I'll just wait for the next opportunity to do so. If in the meantime you find yourself agreeing with the thought experiment which is this article, and have even applied the idea in practice, let me know how it worked out for you!

Finally, some suggestions for further reading:

  • Mathias Verraes has a nice explanation-in-a-gist where he argues that "We don't need no DIC libs", written after a discussion on Twitter about this topic. The gist also includes some implementation examples.
  • Marijn Huizendveld has an interesting blog post about how you can deal in a better way with environment variables (which works well with hand-written service containers too).
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Matthias Noback Blog by Matthias Noback - 5M ago

Cyrille Martraire: Domain modeling towards First Principles

This was a talk from the first day, but it required some more processing before writing about it. Cyrille is one of my favorite speakers. He's fast, funny and brings a lot of interesting topics to the table. So many that it's sometimes hard to keep following his train of thought, and writing down some notes at the same time.

A central concept from his talk was what he called the waterline between IT and the business. In a traditional scenario, developers get provided with "work" on a case-by-case basis. They don't learn about the general idea or plan, or even the vision, goal or need that's behind the "work item". They just have to "implement" it. It leads to badly designed code. But it also leads to the wrong solutions being delivered. If only developers could have talked with the people who actually have the problem for which they build the solution. Maybe there's another problem behind it, or maybe the business has provided the developer with a solution, instead of a problem. To higher the waterline means to get more involved with the customers and users, to understand their problems, and work together on a solution. Make sure you get involved.

When looking for the right solutions, investige the problems and use the following heuristic: "Consider the intensional alternative". Cyrille considers "intensional" a pedantic word and likes it. It's opposed to "extensional". Extensional corresponds to enumarating the results. Intensional means defining the predicate which will produce those results. Looking for intensional solutions means you'll end up with a better understanding of the problem. And also an automatable one.

While collecting these intensional aspects of existing business problems, as a developer you will be building a theory about that business. Cyrille warns against the illusion that a domain is simply a lot of information withs if-s on top. Business is messy, so we shoudn't be obsessed with rules. Some parts of a domain allow theorizing, some don't.

This nicely aligns with my own experience, trying to understand a business domain. Domain experts often aren't aware of the inconsistencies, or impossible rules they come up with. Once you bring in some logic, some rules seem reasonable but just aren't feasible. These may be areas where there's manual intervention in an otherwise automated system.

Another interesting concept Cyrille brought up was that of "skeuomorphism". He noticed that software solutions seem to continue building on technology from the past. Software systems often look like they're rebuilding a paper bureaucracy, but now it's digital. You can't really be "disruptive" if you don't think about radically different ways of solving some problem for the user. This was a bit of shock, because I realized that I often use "paper metaphors" to find out a possible solution for a design issue. Then again, maybe there's room for both - a paper metaphor as a design tool, yet a non-paper bureaucracy inspired software solution.

Maaret Pyhäjärvi: Breaking Illusions with Testing

Identifying herself as a "feedback fairy", Maaret talks about testing, in a very broad sense. It doesn't only cover automated testing, but also exploratory testing, and what you're doing with them. Tests don't break code, they break the illusions you have about it. The way Maaret explained testing, I immediately became scared of all the illusions I have about my code and that I should start breaking them.

I like how Maaret used the term "heuristics", like more DDD-ers do, to share with us how she and other testers do their work. It seems it's very common to share heuristics in the testing community. Very interesting! I don't think programmers share them as freely as they should.

Testing an application starts with realizing: you don't know much. So you have to create some sort of a map of what's there (functions, data, the platform, etc.). Start to discover, and never be bored. Always look for the interesting things, and "poke it until it pops".

In general, what you'll find is all kinds of illusions (they could be called "assumptions", I think). In testing an application ("it has high code coverage, so it's tested") but also in the general activity of developing software ("if we have this new feature, we'll earn more money").

Thinking in terms of illusions, I think most development teams are very good at them. We have all kinds of ideas about our code, our way of working, the application, our teammates, the company, etc. But they aren't really tested. It'll be smart to challenge them.

Also, looking at how others approach their problems is very useful. There will be a different perspective that you didn't think of. Learning is not linear, we are not higher or lower on the scale of "learnedness" or something. We learn different things. So, we have to look for different perspectives deliberately, thereby increasing our ability to shatter illlusions and build good things.

One last idea Maaret shared, one that I wanted to remember is: sharing the pain means more knowledge is going to be shared. I found this very relatable, but it's easy to forget. In every team or company there will be someone who "suffers" from the way they (have to) work, but it's not until they share this pain with someone else, that something is going to be changed about it. And that change is only enabled by the fact that people start sharing knowledge, and will come up with solutions, because they have a different perspective.

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Matthias Noback Blog by Matthias Noback - 5M ago

Eric Evans: Keynote ("Language in Context")

Starting out with the basics (words have meaning within a context; when we make the boundary of this context explicit we end up with a bounded context), Eric discussed two main topics: the Big Ball of Mud, and bounded contexts in the context (no pun intended) of microservices.

Legacy applications are always framed in a negative way, as if it's something to get away from. Personally, I've come to enjoy them a lot. However, there will always be the urge to work around legacy code. The Bubble Context (PDF) can be a great way of creating a new model that works well next to the already existing models. To keep a safe buffer between the new and the old model, you could build an Anti-Corruption Layer (ACL). A fun thing Eric mentioned is that the buffer works in two directions. The ACL also allows the old model to keep functioning without being disturbed by all the new things that are going on in the Bubble Context.

Given that a bounded context may or may not align with an actual subdomain, it may very well be that a legacy context is actually a Big Ball of Mud, with unified models, and simply just a lot of domain knowledge written between the many spaghetti lines that are in there. However, even though it is a mess, and it's getting harder to work with it every day, it may still be what you could call a "Mature Productive Context". The question is: is it still aligned with business views? If it is, we could improve at least maintainability and the cost of change by performing local refactorings. If it isn't, it'll be very hard to change anything. If the basic assumptions of the model change, rework will be very costly.

As a matter of fact, for a project I'm currently working on, we're looking into a module (or context), which requires some heavy refactoring, because it has become technically very hard to work with it. However, the business is still quite happy about it, and it's quite central to its processes.

An important Domain-Driven approach which can be used in the area of legacy code is where you analyze the different subdomains, and find out which ones are generic, and which ones are "core" to the business. As an example, in the aforementioned project there are actually two candidates for context-level improvements. One is related to Sales (which is the heart of this financial application), and one is related to Addressbook records (which is very much supportive to the Sales part). One could say it's even generic, in the sense that an off the shelf solution might be preferable. We wouldn't want to spend a lot of design or development effort there either.

Eric mentioned the term "Quaint Context" as a suitable name for a context that one would consider "legacy". It uses outdated technology probably, and has become hard to maintain. It won't be possible to make big changes there (as mentioned, because these basic assumptions can't easily be changed), so another good name could be "Patch-by-Patch Context".

With a microservice architecture, another option to deal with legacy contexts becomes what Eric calls the "Exposed Legacy Asset" (yet another nice term!). This will be a legacy application which starts to adapt to the microservices environment by producing messages that will be useful for actual microservices in that environment. For instance, database triggers could be used to produce events. The external events themselves don't have to be as low-level as the internal events that caused them.

Eric touches on several other interesting aspects of microservice architecture, but I wanted to briefly mention some other relevant ideas here. Eric looked back at 15 years of Domain-Driven Design and proposed that by now we maybe need a definition of DDD itself. He doesn't want DDD to be just a club, but asks for intellectual honesty. If you try to apply DDD and somehow it fails, you should share this story. If you're skeptical about some aspect of DDD, talk about it. I like how it boils down to focusing on the core domain, exploring models together, and speaking a ubiquitous language in an explicitly bounded context. Nice!

Rebecca Wirfs-Brock: Growing Your Design Heuristics Toolkit

This one was a workshop with limited access, so I was lucky I could attend it. Rebecca had spoken in a previous edition of the conference about heuristics, which triggered my interest in the idea. The workshop was about the process behind it. It had some interesting pointers, like a PDF about the concept and a book by Billy Vaughn Koen: Discussion of the Method. Definitely things to check out!

Rebecca defined "heuristic" as a practical method, a rule of thumb. Heuristics are things you collect over time, when you find out what works best in a certain situation. They may be hints about how to solve a design problem, or how to find out what to do next, or how you should relate to something.

An example of a difficult situation in software development is when you encounter "smelly" code. Something's wrong. It needs fixing, but: do you do it now? Never? Only in certain cases? One could say: if you see a problem, fix it. Another would say: if it isn't broken, don't fix it. Or maybe: never fix it. Don't touch anything at all, because it might break.

We all have some intuitive approach in this matter, and I find it very interesting how:

  1. This approach changes over time.
  2. Many people will have many different approaches.

Rebecca suggested keeping a journal, describing and reflecting on how you actually tackle design issues.

We practiced collecting heuristics according to a somewhat fixed recipe. Here's an example:

Question: Should I fix code smells whenever I encounter them? Heuristic: Only fix them when you're working on the code. Example: If you're just reading the code, don't improve it just yet. If you dive in and make changes because of a new feature was requested, or a bug had to be fixed, add a test and fix the smell.

You can collect these based on your own work, and when talking to someone else you want to learn from, you can start collecting many more. When finding out which heuristics other people use, it helps to challenge them. Ask questions, find out the exceptions, the many other subtle things that should be taken into account, etc.

I found the workshop very interesting and was happy to learn more about this useful concept of a "heuristic".I feel that writing about technology has always been about exposing my own heuristics, or rules of thumb, and by sharing, maybe I can help others too. I think there's a fundemental issue with writing though: it may only "click" with the reader when their experience somewhat overlaps with the experience the writer had. You have to recognize at least part of what's in the book/article, before you can relate to it, or can "accept" it as something useful.

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

All of a sudden it became book-writing season. It began in August when I started revising my second book, "Principles of Package Design". Apress had contacted me about adopting it, and they didn't want to change a lot about it. However, the book was from 2015 and although I had aimed for it to be "timeless", some parts needed an update. Furthermore, I had happily pressed the "Release" button back then, but it's the same as with software development: the code you wrote last year, you wouldn't approve of today.

Upgrades

Because Apress has their own pipeline for manuscript to book conversion, I had to take the original Leanpub-flavored Markdown manuscript, export it to HTML, then copy it into Mac Pages, and finally export it as a Word document. That was already a lot of work. Then I started reading the book and collected all the issues, creating post-its along the way. Having every little issue on display was a nice trick. It made progress visible, and made it feel like a project I could finish.

Re-reading my own book was a very interesting experience. I noticed how often I'd been lazy and skipped a proper argument for a piece of advice. I also noticed how some advice wasn't very clear and could easily be misinterpreted.

In that regard, it was very, very helpful to have Ross Tuck on board as a technical reviewer. He pointed out several issues where the reader, given this or that background, could have difficulty understanding a section, or take unintended advice from it. Ross put in a lot of time and effort, so from this place, thanks again!

Besides revising, I've also added several new sections, most notably about the following topics:

  • The reuse of code from the Domain layer, with a discussion about Domain-Driven Design.
  • Why "final" classes should be preferred, and how composing objects should be the preferred way of changing the behavior of existing objects.
  • When to introduce an interface for a (packaged) class.

Because there are many people who have read the first edition, who I don't want to "force" to buy the second edition as well, I've already published several articles that cover more or less the same ground:

Without further ado

So, here we are. The release of the second edition of Principles of Package Design! The book is available on Apress,com and Amazon, but it's also a "regular" book, so your book store should be able to order it as well.

Buy the e-book or soft cover via Apress

Buy the e-book or soft cover via Amazon

Do you want to review the book?

I'm very curious about your opinion. If you've read the book (first or second edition), please let me know what you think of it. It would be great if you could submit a customer review on Amazon.

If you'd be interested in writing a review on your website, blog, etc., send me an email at info@matthiasnoback.nl, so I can send you a review copy.

Also, if you see this book in a store somewhere, it'd be very cool if you could send me a picture!

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

In part 1 of this short series (it's going to end with this article) we covered how you can test-drive the queries in a repository class. Returning query results is only part of the job of a repository though. The other part is to store objects (entities), retrieve them using something like a save() and a getById() method, and possibly delete them. Some people will implement these two jobs in one repository class, some like to use two or even many repositories for this. When you have a separate write and read model (CQRS), the read model repositories will have the querying functionality (e.g. find me all the active products), the write model repositories will have the store/retrieve/delete functionality.

In particular if you write your own mapping code (like I've been doing a lot recently), you need to write some extra tests to verify that the persistence-related activities of your repository function correctly.

Writing tests for store and retrieve actions

When you're testing a class, you're actually specifying its behavior. But you're doing that from an outsider's perspective. The test case uses an instance of your class (the subject-under-test). It calls some methods on it and checks if the resulting behavior is as expected.

How would you specify the behavior of a save() method? You could say about the repository: "It can save an entity". How would you verify that a given repository class implements this specification correctly? If the repository would use Doctrine ORM, you could set up a mock for the EntityManager or a similar class/interface, and verify that it passes the object to its persist() method. However, as explained earlier, within repository classes mock's aren't allowed. The other option would be to make a call to that save() method and afterwards look inside the database to verify that the expected records have been inserted. However, this would tie the test to the implementation of the repository.

It seems there's no easy way in which you can find out if a call to save() has worked. But let's think about that save() method. Why is it there? Because we want to be able to later retrieve the entity that it saves. So, what if we test our save() method by also introducing its counterpart, getById()? That way, we can indirectly find out if save() has worked: getById() is expected to return an object that's equal to the object you've just persisted.

In other words, a black box test for save() can be written if you combine that test with getById():

public function it_can_save_and_retrieve_an_entity(): void
{
    // Create a basic version of the entity and store it
    $originalEntity = ...;
    $this->repository->save($originalEntity);

    // Now load it from the database
    $entityFromDatabase = $this->repository->getById($originalEntity->entityId());

    // Compare it to the entity you created for this test
    self::assertEquals($originalEntity, $entityFromDatabase);
}
State changes, child entities, etc.

Usually an entity doesn't get persisted once. You'll be modifying it, persisting it again, adding child entities to it, removing them again, etc. So, for every situation like this, I like to write another test method, showing that all this really works, e.g.

public function it_can_save_child_entities(): void
{
    // Create a basic version of the entity and store it
    $originalEntity = ...;
    // Add some child entity
    $originalEntity->addChildEntity(...);
    $this->repository->save($originalEntity);

    // Now load it from the database
    $entityFromDatabase = $this->repository->getById($originalEntity->entityId());

    // Compare it to the entity as we've set it up for this test
    self::assertEquals($originalEntity, $entityFromDatabase);
}

Sometimes it makes sense to add intermediate save() and getById() calls, e.g.

public function it_can_save_child_entities(): void
{
    // Create a basic version of the entity and store it
    $originalEntity = ...;
    $this->repository->save($originalEntity);
    // Load and save again, now with an added child entity
    $originalEntity = $this->repository->getById($originalEntity->entityId());
    $originalEntity->addChildEntity(...);
    $this->repository->save($originalEntity);

    // Now load it from the database
    $entityFromDatabase = $this->repository->getById($originalEntity->entityId());

    // Compare it to the entity as we've set it up for this test
    self::assertEquals($originalEntity, $entityFromDatabase);
}
Deleting entities

Finally, a repository may offer a delete() method. This one needs testing too. Deleting is always scary, in particular if you somehow forget to add proper WHERE clauses to your DELETE statements (who didn't, at least once?).

So we should verify that everything related to a single entity has been deleted, but nothing else. How can you do this? Well, if you want black box testing again, you could save two entities, delete one, and check that the other one still exists:

public function it_can_delete_an_entity(): void
{
    // Create the entity
    $originalEntity = ...;
    $originalEntity->addChildEntity(...);
    $this->repository->save($originalEntity);

    // Create another entity
    $anotherEntity = ...;
    $anotherEntity->addChildEntity(...);
    $this->repository->save($anotherEntity);

    // Now delete that other entity
    $this->repository->delete($anotherEntity);

    // Verify that the first entity still exists
    $entityFromDatabase = $this->repository->getById($originalEntity->entityId());
    self::assertEquals($originalEntity, $entityFromDatabase);
}

Or, if you like, you could let go of the black box aspect and populate the database with some entity and child entity records that you want to prove will still exist after you delete a single entity.

Ports & adapters

If you write purely black box tests for your write model entity/aggregate repository (that is, for save(), getById() and delete()), the test cases themselves won't mention anything about the underlying storage mechanism of the repository. You won't find any SQL queries in your test. This means that you could rewrite the repository to use a completely different storage mechanism, and your test wouldn't need to be modified.

This amounts to the same thing as applying a Ports and Adapters architectural style, where you separate the port (i.e. "Persistence") from its specific adapter (i.e. a repository implementation that uses SQL). This is very useful, because it allows you to write fast acceptance tests for your application against code in the Application layer, and use stand-ins or "fakes" for your repositories. It also helps you decouple domain logic from infrastructure concerns, allowing you to replace that infrastructure code and migrate to other libraries or frameworks whenever you want to.

Conclusion

By writing all these repository tests you specify what the repository should be capable of, from the perspective of its users. Specifying and verifying different uses case proves that the repository is indeed capable of storing, retrieving and deleting its entities correctly. Most effective for this purpose is black box testing, where you make sure the repository test is completely decoupled from the repository's underlying storage mechanism. If you can accomplish this, you can rewrite your repository using a different storage mechanism, and prove that everything still works afterwards.

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

There's something I've only been doing since a year or so, and I've come to like it a lot. My previous testing experiences were mostly at the level of unit tests or functional/system tests. What was left out of the equation was integration tests. The perfect example of which is a test that proves that your custom repository class works well with the type of database that you use for the project, and possibly the ORM or database abstraction library you use to talk with that database. A test for a repository can't be a unit test; that wouldn't make sense. You'd leave a lot of assumptions untested. So, no mocking is allowed.

But how do you test everything that is going on in a repository? Well, I found out a nice way of doing so, one that even allows you to use some kind of test-driven approach. In this article I'll cover one of the two main use cases for repositories: querying the database, and returning some objects for it. The other use case - storing and loading objects - will be discussed in another article.

What's a query?

In essence, a query defines criteria for selecting certain records from the database. Comparing it to the real-world equivalent: you'd have some kind of room full of stuff and you let a robot go in with a list of selection criteria. It examines things one by one to see if it matches those criteria, and if it does, it takes the thing with it, and brings it to you. How can you verify that the robot works well? Sure, you put some things in that room that match the criteria, and see if it fetches those things for you. However, you should also try dropping some things in the room that wouldn't match the criteria you gave to the robot, and verify that the robot doesn't take those things too.

For repository classes it should work the same way. Testing if some query in the repository class works, means that you should load some fixtures for records that would match the criteria. But you shouldn't forget to add some records that wouldn't match the criteria too. Otherwise, you wouldn't notice the difference between your elaborate SELECT query and a SELECT * WHERE 1 query. So basically you'd have to come up with examples, as well as counter-examples. It turns out that these counter-examples can be used to test-drive the query itself.

As an example, consider you need a query that will return a list of products, but only those which are active. You'd start with the simplest situation; a single product that should be returned:

INSERT INTO products (product_id) VALUES (1);

You then write the query for this:

$result = $this->connection
    ->createQuery()
    ->from('products')
    ->select('*')
    ->execute()
    ->fetchAll();

In your test you can then verify that this query indeed loads this one product. At this point I often add another record to the database, to prove that the query is capable of returning more than one object (i.e. there's no "limit" in place or anything).

INSERT INTO products (product_id) VALUES (1);
INSERT INTO products (product_id) VALUES (2);
Implement the first requirement

The query we wrote doesn't yet take the "is active" flag for products into consideration. So now, before you dive in and modify the query, you need to show that the code as it is doesn't yet implement all the requirements. So you add a counter-example; an inactive product:

- Active products
INSERT INTO products (product_id, is_active) VALUES (1, 1);
INSERT INTO products (product_id, is_active) VALUES (2, 1);

- Inactive product - should be ignored
INSERT INTO products (product_id, is_active) VALUES (3, 0);
A failing test

If you run the test again, it will fail, because it returns an extra product that wasn't expected to be there. This is the traditional "red" phase of TDD.

You then modify the query, adding a "where" clause that will exclude inactive products:

$result = $this->connection
    // ...    
    ->andWhere('active = 1')
    // ...
    ->fetchAll();

Run the test again, and the light will be green again.

Green; implement another requirement

Now you can continue working on the next requirement. In this example, we need the product to be in a group of products that's marked as "stock" products. With every extra requirement, we run into another variation we'd need to check. Consider a product that is active, but is not in the right kind of product group; we have to add a counter-example for that:

- Active products (1, 2)
INSERT INTO products (product_id, is_active) VALUES (1, 1);
INSERT INTO products (product_id, is_active) VALUES (2, 1);

- Inactive product (3) - should be ignored
INSERT INTO products (product_id, is_active) VALUES (3, 0);

- Active product, but in a non-stock product group (100) - should be ignored
INSERT INTO product_groups (product_group_id, has_stock_products) VALUES (100, 0);
INSERT INTO products (product_id, is_active, product_group_id) VALUES (4, 1, 100);

Running the tests will make the new product pop up of course, something we don't want, so we need to modify the query:

$result = $this->connection
    // ...    
    ->andWhere('active = 1')
    ->innerJoin(
        'products', 
        'product_groups',
        'product_groups.product_group_id = products.group_id'
    )
    ->andWhere('product_groups.has_stock_products = 1')
    // ...
    ->fetchAll();

This would seem to make it work, but when we re-run the tests now, the result is empty. Products 1 and 2 aren't in a product group yet, so the "inner join" will filter them out. So we have to modify the fixtures to fix this. The same goes for product 3 actually; we should put it in a stock product group, to verify that the "active" flag is still taken into account:

- Active products (1, 2), in a stock-product group (101)
INSERT INTO products (product_id, is_active, product_group_id) VALUES (1, 1, 101);
INSERT INTO products (product_id, is_active, product_group_id) VALUES (2, 1, 101);
INSERT INTO product_groups (product_group_id, stock_products) VALUES (101, 1);

- Inactive product (3), in a stock-product group (101) - should be ignored
INSERT INTO products (product_id, is_active, product_group_id) VALUES (3, 0, 101);

- Active product (4), but in a non-stock product group (100) - should be ignored
INSERT INTO product_groups (product_group_id, has_stock_products) VALUES (100, 0);
INSERT INTO products (product_id, is_active, product_group_id) VALUES (4, 1, 100);

And so on, and so on. For every new requirement, first add a counter-example to the fixtures, then see how it pops up in the query result. Then modify the query to ensure that it doesn't. This is the Red - Green - (Refactor) TDD cycle for queries in repository classes. By the way, I find it helpful to add important IDs or other characteristics as a comment to the fixtures, so it's easy for the reader to figure out what's so special about a certain record.

Helpful?

I find this approach very helpful. It helps you take small steps when working on repositories, where you may often feel insecure about getting the queries right (and understanding your ORM well). Test-driving your queries can prevent you from making stupid mistakes when loading records for specific owners (tenants, users). With this approach you can actually prove that you're loading the right things by adding some records owned by other tenants and users in your fixtures.

Just like with unit tests, it also helps make the code satisfy new requirements, whenever they become relevant. You have a good starting point for making amendments, a clear view on what's there, and a safety net in case you're worried that modifying the query in some way will accidentally result in unwanted records being loaded.

Potential problems

So far I've found that implementing more requirements can become a bit more tedious. You'd have to add more and more counter-examples. In particular if you also want to test how the repository deals with different data and data types, and you want to verify that it hydrates objects correctly.

Still, being able to safely write queries is something that I've wanted for a long time, and now that I can do it, I'm no longer worried as much if I see the correct data on my screen. More than once I've made the mistake of only testing repositories by clicking through the pages in the browser. Seeing anything at all on the screen was sufficient "proof" to me that the query I wrote was correct. Of course, in practice, it often turned out there was an awful mistake hidden in the query, and the data on the screen wasn't the right data at all. With this test-driven approach however, the test has already proven that the repository loads the correct records, nothing more, nothing less.

Read for later

Articles marked as Favorite are saved for later viewing.
close
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview