Value Objects and Immutability

Following on from my previous post on value objects,
immutability is often given as a core aspect of value objects. There is good reason for this, but it is not immutability that make an object a value object. It is equality based on value not identity that is essential.

That said, immutability makes sense for the domain concept in most cases. Going back to the Takeaway example from my previous post and the Distance value object used for the radius of the DeliveryArea. If the delivery area was expanded then it would be to a new distance, the distance itself would not change. If the radius was 5km and it was increased to 8km then it is a different distance, 5km is still 5km, it’s just that the radius is now 8km.

In fact, since we are thinking like that, the DeliveryArea itself is a different delivery area if we change the distance. So it makes sense to set the delivery area property of the Takeaway to a new DeliveryArea rather than mutate the existing DeliveryArea.

So how do we implement this? The objects are immutable as they stand anyway. They are constructed with values and there is no way of mutating these from outside. Where we need something more that this though is if, rather than just changing the distance, we wanted to be able to add to it. For example, if we have a distance of 5km and we want to add 1 km to this. We could do this externally to the value object by getting its current value adding the new value to this and then creating a new one. We could simplify this though by getting telling the object what you want to add to it and it returning a new object with the total:

There may be times though when an object is immutable in terms of the domain but technical constraints mean that it is not actual immutable. This is can occur when construction needs to be done through setter methods in order to allow integration with something else, such as a framework’s way of constructing objects from a form. Whilst we want to stay decoupled from frameworks as much as possible, value objects can still be something that are used at the boundaries between the model and the framework. In this case it may make sense to make it mutable to avoid jumping through hoops. It can still then be treated as immutable within the domain model.

Value Objects

Since PHP Value Objects is now a thing, I thought I’d write about them as well. You may also want to read http://kacper.gunia.me/blog/ddd-building-blocks-in-php-value-object and http://kacper.gunia.me/blog/validating-value-objects.

So Value Objects are objects that are considered equal based on their values and not on identity. For the sake of having an example, let’s say that our domain is ordering takeaway food for delivery. So within this we have the concept of a Takeaway as a place we can order food from. Since that is what we call them here even if they only deliver food, for, er, reasons.

Typically we would model this with an entity and not a value object. This does indeed fit well since equality between two takeaways is not determined by value. For now, let’s assume the only property they have are their names. If we have two takeaways with the same name they are not the same takeaway:

A takeaway that changes its name can still be the same takeaway:

So if we are deciding whether they are equal we have some identity beyond just values such as its name.

If we consider just the name itself though then two names are the same if they are the same value. This is clearly true with strings:

If we created an object to encapsulate the name then this needs not have identity but can just be consider equal if the value is the same:

So if we want to model the Takeaway’s name as an object then a value object is a good fit. Some value objects, such as the TakeawayName are about single values but trying to focus on the benefits of making that an object are perhaps a little misleading. Instead let’s look at where we might want a value object to have more than one property.

So what other properties might our Takeaway entity have if it just stores them as primitive values? One important domain concept is the area that they will deliver to. For now let’s say assume a simplistic concept of this where it is determined by the location and a distance from that location. This will then form a circle which they will deliver within. The distance can be provided in kilometers or miles as well so we will need to know which it is. So we have the following:

So let’s consider the distance. This is made up of two values, the unit of measurement and the amount of that measurement. The amount alone e.g. 6 is not a distance. The unit of distance e.g. km is not a distance. 6 km is a distance.

Both need to be equal for a distance to be considered equal:

  • 5 km = 5 km
  • 5km != 5cm
  • 5Km != 8km

So using primitives as two separate fields in the Takeaway entity is not fulling capturing the connection between these values. It is possible to change just the unit or just the quantity which does not seem like the correct behaviour. Instead we can extract an object that represents a distance, then we can change the whole distance object instead.

Likewise lat and long as separate properties don’t seem right, what we are really interested in is the location they determine. So let’s make that an object as well and make the reason we are interested in these values more explicit in our model.

So value objects need not wrap a single primitive value. As well as this, value objects do not need to just contain primitives. What we are really interested in here is the area the company are willing to deliver to. The distance and location do not capture and make explicit this concept in out code. So let’s extract an object that represents the area covered. This can be made up of the location and distance value objects and not have any primitive values itself.

Going back to the start again, these are value objects because there equality is determined by values and not by identity. The area covered still has no identity, we can swap it with another object with the same values without any issues. None of this says anything about behviour though; being a value object does not mean having no behaviour and just values.

So if we have a location and we want to find out if the company will deliver to it then we can ask the company object:

This method could calculate whether the location falls in the area itself but it would be simpler to just ask the Area object if the location falls in it:

Not only can the value object have behaviour but it attracts it. It is better for the DeliveryArea to decide whether the Location is included or not than have the Takeaway reach into the DeliveryArea to get its values and make the decision. This breaks encapsulation and by making it a method on the DeliveryArea it can be called from elsewhere. Putting the logic involved in checking in the Takeaway ties it to the wrong object.

So our company object now just has an AreaCovered object and a method that delegates to it for deciding if a location is within it. One thing that stands out here is that the company no longer knows anything about what that Area is or what the location is. When we started they were tied to lat, long, and a radius. Should alternative ways of identifying locations and areas – e.g. a list of postcodes that is covered then nothing needs changing in the company object to support this. Different implementations of the Location and AreaCovered objects could be used for this. We could extract interfaces for Location and AreaCovered and have different implementations without changing the Takeaway entity at all.

This encapsulation of data and polymorphism are the benefits of OO. If we just had the company object with primitives and that had the logic of deciding is a location was covered then supporting different area and location types would be much more difficult. We have introduced more objects, where each one is in itself very simple.

As well as this, the company object’s level of abstraction is higher now. We can see that it has an area that is covered and that we can find out whether a location falls in it. For many purposes this may be all we need to know when reading the code. We need not concern ourselves with the detail of how these things are implemented. Without this level of abstraction we would see that a company has a latitude, a longitude, a distance amount and a distance unit and some logic around these value. This tells us too much about the detail and not enough about the purpose and this will only get worse if we want to support more ways of representing these things.

So value objects are useful for encapsulating data and exposing related behavior. Well, yes, but that’s not specific to value objects that is Object Orientation.

So what about immutability and validation? Well they are not unimportant but they are not what value objects are primarily about. Objects are about encapsulation and polymorphism. Value objects are the subset of objects where equality is determined by value and not identity.

More on those things soon anyway.

The new Symfony Best Practices

Yesterday the initial preview of the new Symfony Best Practices was made available. A document like this is something that the Symfony community has needed. It is a much appreciated effort by those involved in producing this. The existing documentation is great but in order to introduce individual concepts it is necessary to simplify things whether it is the concept being introduced or others to avoid being sidetracked and bogged down in details other than the one at hand.

Symfony is a powerful, flexible framework which favours configuration over convention in general. There are conventions which can be followed in some cases but configured different if you have a particular need to. This means that it can be confusing for those moving from first starting out learning the framework into building applications with it. Faced with a lot of decisions which options should they choose?

The new best practices document does a good job of providing this next step. I have seen a lot of Symfony applications that would have been vastly improved had this guide been followed by those developing it. It has however not been entirely well received by the community with many questioning whether things in it are really best practices.

It was developed not by the community as a whole but by a fairly small group before being released. This in itself has made it difficult for some to accept but it was probably necessary if anything was ever going to be complete given the disagreements about content that would have made developing it openly a torturous process. Those involved in writing it are very well placed to have an in depth knowledge of the framework and a wealth of experience in creating applications with it. The repository for the document is to be opened up soon anyway so this will not be the case as it evolves.

I think there are some changes to it that could have and could continue to avoid some of the controversy and to better explain the purpose of the document. There are plenty of articles on why “best practices” is not a good name for documents like this. I won’t regurgitate them here but it being misleading that they are the one true way to do things and therefore anything else is inferior is a concern. Another is that best implies that there is little room for improvement. Whilst there are disclaimers included early on to say that these are optional and personal opinion a change of name would make this clearer.

The document opens by talking about how Symfony is suitable for a whole range of applications sizes and complexities up to enterprise software. I think this is true but that much of what follows makes a lot of assumptions about the nature of the application being built and only applies in those circumstances. Yes, a lot of applications built with Symfony will be relatively simple, database backed, CRUD applications. However there are many applications that this does not describe. A not inconsiderable number of people are using Symfony to build APIs, perhaps with a separate website frontend using a client side framework to communicate to this API. In that case many of the practices are not relevant or counter productive. Likewise many people do work on applications that communicate with other systems via HTTP clients, message queues and other means. These are not merely fringe cases and dealing with the complexity of multiple and potentially changing data sources is a real day to day issue for many not just an academic concern.

Making it clear that these are guidelines for building a certain type of application and not suitable in all cases would be good improvement in my opinion. Even better would be to provide some guidelines for working on applications outside of this relatively narrow remit. It is when moving past that point that a lot of questions about what approach to take arise in my experience.

In terms of specific content, there is some I disagree with but that is neither here nor there really. I do find that there is currently some confusion in it as to what the priority is. There are always factors pulling in different directions when building an application. Which of these are important depends on the individual application and the context it is being developed in.

Convenience and speed of development are important but can be odds with performance. Making full use of the short cuts and conventions that the framework provides doesn’t always fit well with making sure that it can be understood and maintained by some that lacks that in depth framework specific knowledge. The best practices seem a little muddled on this, param converters are good because they save time and boilerplate code but the @Template annotation is bad because some prior knowledge is needed and there is a performance issue. I don’t see there being much of a difference in the amount of prior knowledge required in each case. The performance issue of @Template may be a concern but depending on the project this may be less of a concern than the amount of developer time it saves.

Rather than single a single one as being the best, it may be better to say which is better for performance, for convenience or for clarity, when these pull in different directions. This still gives a clear recommendation to someone who knows which of these is more important to their application.

There is something I see no reason not to change to improve the perception of this document amongst the existing community and to avoid giving a bad impression to newcomers. That is the use of some unnecessarily inflammatory and opinionated language. The first thing that jumps out and no doubt was what rubbed a lot of people up the wrong way was the second paragraph:

“These community resources – like blog posts or presentations – have created an unofficial set of recommendations for developing Symfony applications. Unfortunately, a lot of these recommendations are in fact wrong.”

If this document is, as it states immediately after this, a subjective opinion then opening by dismissing a huge collective effort from the community as “in fact wrong” seems completely out of place in a document of this nature.

Likewise when talking about decoupling controllers then this does not seem like a calm and measured argument:

“And since your controllers should be thin and contain nothing more than a few lines of glue-code, spending hours trying to decouple them from your framework doesn’t benefit you in the long run. The amount of time wasted isn’t worth the benefit.”

Some people may benefit from doing this and saying that this is wasted time seems unnecessarily antagonistic attack on those with opposing views. The same advice could be presented in a much more neutral way in my opinion. There are better places for strong personal feelings about this topic to be shared.

So to reiterate, I think this is a much needed document and the effort that has gone into getting it to us is much appreciated. I personally think it could benefit from a change of name and some clarity about the nature of the applications it is suitable for. In some places it could be a bit less prescriptive and instead make recommendations based on the application’s needs and context. Longer terms further guidelines that cover some of the wider picture of different application being created with Symfony would be great. However, I do see no reason not to remove some of the more emotional and inflammatory language as soon as possible.

The repository is to be made public so that the community can contribute. I hope that this can be productive and further improve the excellent documentation that Symfony already benefits from.

Some, no doubt unoriginal, thoughts on BDD

BDD is primarily about designing object behaviour and interaction and not about testing. It does however produce a suite of regression tests as a side effect. This has value in allowing us to refactor code knowing that it still behaves as specified.

One issue people have with (T|B)DD is that it then makes it difficult to change things as you have lots of tests/specs to change. If the behaviour is changing then they should change. So is this a red herring? Well not exactly, this only holds true if you use only use them to specify the external behaviour at a boundary. For example, from the DDD world, an aggregate boundary.

If inside that boundary there are multiple objects and how they communicate is essentially an implementation detail then specs for these may well get in the way when refactoring the implementation within that boundary. The behaviour of the objects implementing maybe changing a lot without the external boundary changing.

It seems one view is that you should therefore avoid writing them in the first place and another is that this is the right thing to do and you should just maintain them and live with this. So, does this mean that you should not write these specs in the first place? Do you need to then keep updating these to change the implementation?

Well as Hegel would have it, there is a possible synthesis of these positions. Use specs to design the internal interactions where it helps you but don’t feel obliged to keep these as soon as they becomes a hindrance to improving that implementation later. Keep those that specify the external behaviour but discard those that are no longer helpful. The test suite is a side effect, its not something to be precious about if it gets in the way of the primary use of BDD as a design technique. Don’t throw the baby out with the bathwater though – if that side effect gets in the way don’t avoid the original primary benefit.

Symfony2: Configuring different services for different environments

In my previous post I talked about avoiding optional dependencies. The example I used was of changing an optional dependency on a logger into a mandatory one. We injected a null logger implementation when we did not need logging. In this post I am going to look at this from the configuration point of view. In particular, how we could switch between which implementation gets injected for different environments.

We would not want to turn logging off for production but we may want to for an environment used for automated tests.

So we have the following services file:

For our test environment we want to use the null logger implementation from PSR instead. So we need to inject a different logger service into all the services that use it. So what can we do? We could load a different xml file for the test environment by changing the extension class.

If we load a different one instead then we will have a lot of repetition. We would be better off loading an extra one and overriding services as necessary. We could define a new logger service and redefine all the services that use it:

This is still a lot of repeated configuration. We could redefine the logger service instead so that this is then used throughout.

This is an improvement as we now just have one different service in our extra service file. It would be good to reduce the changes further still. We can do this using a service alias. Then we can just change what the alias looks like. We can create an alias that points at the normal logger service. Then we can use this in our other services:

We can now set the alias to point at the normal logger service in our main service file. We can rename the null logger service id. Now that it does not clash with the main logger service we can move it to the main services xml file. In the extra services file we now just need to point the alias at the null service.

This does not make much difference as the null logger has no arguments. We could get rid of it altogether though. We can make the choice of service to alias to a parameter:

It can now be set in the application config and the extra services file removed altogether!

and

As well as making the reconfiguration simpler we have decoupled the choice from the environment. I have previously written about this as a more general idea. We can now change the parameter on its own. This may not be that important for our logger but allows us to switch which services we use with ease.

Avoiding Optional Dependencies

In my previous post I argued against setter injection. Optional dependencies are one of the main objections raised in the comments and elsewhere. I did mention these with a suggestion of just making them optional constructor arguments. I also mentioned that refactoring to stop it being optional was a solution.

I think that this is worth exploring further. I think that the disadvantages of setter injection means it should be avoided. I do not think any advantages for dealing with optional dependencies outweigh the disadvantages. This is not an opinion shared by all though.

Looking at how I deal with optional dependencies made me realise that I do not just avoid setter injection. I also usually avoid having optional dependencies. Having thought this through more will now avoid using them at all.

So why are optional dependencies a problem? Often an optional dependency is a sign that the class has several behaviours. So it has several responsibilities and is not adhering to the Single Responsibility Principle. A class with an optional dependency does one thing always. It does another thing sometimes if the optional dependency is present.

We would be better extracting the optional behaviour into another class. This new class could then have a single responsibility for that behaviour. In the new class this would not be optional and we can now choose which version. We are now deciding if we want the optional behaviour only in the config not in the config and in the object.

Even if this is not possible then we would still do well to avoid the optional dependency. Consider logging as the optional behaviour, which was a common counterexample. So a class with optional logging may look like this:

There is another issue here which is that we have to wrap any use of the logger is a conditional. If we do not then we would get a fatal error when calling the non-existent logger’s method. This adds complexity to the class and raises the risk of bugs. It is easy to avoid this bug with a spec or unit test. We could avoid even having to go to that effort though. Our class would be simpler if it looked like this:

We can have this and still achieve the desired behaviour of optional logging by using a Null object. We can pass in a null logger that implements the logger interface but which does nothing when we do not want logging. Our class does not need to be aware that this is the case, it will use anything that has the interface. In fact if you are using a PSR3 logger then there is null logger implementation in the psr/log package.

Yes, we need an extra class but optional logging is something we will find in many classes. For the sake of a single extra class we can remove conditionals from a lot of places in our code. This reduces the number of potential bugs and makes the code more readable. Removing a lot of conditionals in exchange for creating a single class is a good refactoring of code.

It is no more complicated to turn logging on and off in configuration. When we do not need logging the logger service can just be the null logger. We will have turned off logging without making any other changes.

We now have simpler code. We can remove all the conditionals. We avoid setter injection. All without adding anything more complex to our code than a simple null implementation of the logger.

My thought is that optional dependencies are themselves a code smell. Setter injection is not a solution to this smell. Stopping the dependency being optional is more important than how we inject it.

Avoiding Setter Injection

A while ago I had a twitter discussion with Igor Wiedler and Matthias Noback about setter injected dependencies being mutable.

When using objects as services in an application then we do not want them to have state. This is because we can them call them many times without the outcome changing. For example, if we have a mailer service that sends emails for us we would expect it to behave the same way whenever we call it in an application. We would not want the service’s behaviour to be different because we had already called it somewhere else in the request. We can make sure that we write the internals of services to not change their state in this way. Yet when we use setter injection we take control over this from the service and leave it in the hands of the code consuming the service.

The two main ways of injecting dependencies are constructor and setter injection. A main advantage of constructor injection is that ensures that we inject the dependency. This means that there are no surprises later if it has not been. Another advantage is that the choice of dependency is immutable. Constructing the service fixes it to that dependency (assuming we provide no other means of changing it).

With setter injection, we can call the setter method many times. So the choice of dependency is not immutable with setter injection. We cannot use the service knowing we will get the same results with the same arguments. Other code using the service could have changed the dependency between calls. Anywhere that uses the service can now set a different dependency which will change its behaviour.

For example, let’s say we can set a filter on our mailer service. Since this is optional we choose to inject it with a setter method. If we do not want a filter then we do not call the method. We could not expect consistent results if other code as other could change the filter dependency.

We could change the setter to only allow the dependency to be set once. We will need to write extra code needed for this. The main issue it will fall down because the of the optional nature of dependencies provided by setter injection. The behaviour remaining the same throughout the service’s lifetime is still not guaranteed. We could use the service before the dependency has been set and again after it has been set with different results.

We can avoid this by injecting the dependency through the constructor and making it an optional argument. Now the dependency is still optional but we are fixing whether it is set at the time of construction.

As an aside, it would be better to refactor away from having optional dependencies. Instead we could move to different classes implementing the service’s interface. One without the dependency and one where it is mandatory.

So is there a case for using setter injection at all? One other use is as an “adder” method which adds dependencies to a collection. Here calling it again does not replace the dependency but adds it to the collection. We still have the potential for changing behaviour here by adding additionally dependencies later.

If we want to fix the collection of dependencies what can we do? Well we can fix them at construction time by passing the full collection in as a constructor argument. So, is this always possible? Symfony2 uses adder methods to add dependencies outside of the original service definition. These dependencies are usually added in a compiler pass. The compiler pass finds tagged services and adds them as dependencies to another service. It does this by adding a call to the service’s adder method for each dependency in the service’s definition. Here is an example from the Symfony documentation:

The problem here is that it is still possible for code using the service to add more dependencies later. So can we prevent this? In the twitter conversation We discussed several options. One was to have a way of flagging that we have finished adding dependencies. The method could then throw an exception if called after the flag is set. We could do this by adding a method that sets the flag, calling it last in the service definition. Unfortunately, this feels like quite an awkward solution and relies on making sure that you add the call to the locking method.

Another would be to have a builder object that collects the dependencies and then set the service up with them. This requires adding an extra layer of complexity, which is not appealing. In fact, we realised that the service container already is doing this job. Just changing the way the compiler pass works would be enough. The TransportChain class’s first constructor argument is now a collection of transports. The compiler pass could now be as follows:

Here we are getting the first argument in the service definition for the transport chain. In this case we are assuming that it is already defined as a collection. We then append the references to the tagged services to the collection keeping any existing services. The argument in the definition is then replaced with the populated collection. We can now remove the addTransport method from the class. This prevents code using the transport chain from making further changes.

So it looks as though we can avoid most instances of setter injection with little difficulty. We then give ourselves the greater safety of constructor injection.

Symfony2: Some links

In my previous post on separating bundles from libraries in Symfony2 applications I said I would follow this up with more details.

I have yet to write that post, in the meantime some links to useful information along these lines:

I spoke about this subject at SymfonyLive in London, you can find the slides here.

A lot of the above related to using the ideas of Domain Driven Design in Symfony2. Eric Evan’s original Blue Book DDD book and Vaughn Vernon’s Implementing DDD book are both excellent if not easy reads as they are both long with a lot of information packed in.

Some slide from recent conference talks: Marcello Duarte and Konstantin Kudryashov spoke about the framework as an implementation detail at Symfony Live in London. At the PHPNW conference this weekend Ross Tuck gave an excellent talk on a similar topic as well, Models and Service Layers; Hemoglobin and Hobgoblins.

Symfony2: Bundle structure – bundles as integration layers for libraries

Many of the bundles that are part of the Symfony2 framework and the available third party bundles are effectively integration layers between a library and the framework. For example:

What is in the bundle then? One common thing are service definitions to integrate them into a Symfony2 app instead of manually creating objects. Alongside this is exposing the configuration of these objects via the Dependency Injection Extension to allow configuration from the application’s overall config files.

The libraries in many cases have been written using dependency injection but the definitions that wire them together are part of the bundle. In some cases the libraries are not written using dependency injection but the bundle can still contain the service definitions to integrate them into a Symfony application.

Service definitions are not the only thing that bundles do to integrate libraries there is also twig extensions, twig templates, some have controllers, listeners for events etc.

In some cases these could be further separated into a bridge and a bundle as with the framework itself. For example, twig extensions and doctrine listeners are not Symfony2 specific and could live in a bridge with the bundle handling service definitions with tags to automatically register them in the application. This would allow other non-Symfony2 applications but which used Doctrine or Twig to make use of the templates and listeners.

This technique also suits code within an application, rather than having everything in a bundle it make sense to split out non-framework specific code into a library and have an associated bundle to integrate this back in. By avoiding having framework specific code you open up the possibility of reusing this library in other applications with no or another framework. Even where it is not useful in another application it will aid any future migrations of the app. This way the bundle is dependant on the app but not vice versa making it much easier to move.

This is not to say that no Symfony code should be used in the library, it makes sense to still use the components but avoiding code from the bundles and bridges makes sense as you are then introducing dependencies only on the components used and not on the whole framework. These can of course be specified in the bundle’s composer.json file so that it can be easily installed standalone elsewhere.

The obvious cases for this are where the code being extracted from the bundle into a library is infrastructure code as it is with most third party bundle/library combinations. For example, if there are particular logging requirements for the application not met by existing solutions, such as having to interact with an in-house log aggregation server. By keeping everything related to this in a separate library and bundle it is then available for use in other applications as well as making any future migrations simpler. It also makes it easier to avoid this code becoming dependant on the needs or worse the business logic of the particular application, which should not be the case for this sort of infrastructure code.

What about the business logic that is specific to the application though? Is there any benefit to doing the same with this? It may seem like it will not be any use outside of this application but if it is part of the business then it may well be useful in other applications even is this is not immediately apparent. Even if it is not used in a different application then any future migrations, even if just to a new major version of Symfony rather than a different framework, will be considerably easier if the business logic has no framework dependencies.

Another benefit is the enforced separation this gets us between the application specific logic and the business logic. Even if we never intend to and don’t use the business logic outside of this application then making it work outside of the application will help us to create business logic that is decoupled from the application and easier to maintain and extend for it. We will no longer end to making decisions about business logic based on the needs of the framework.

Unlike the infrastructure code where it relatively easier to separate out what is not framework specific it becomes harder in the case of business logic, if we move our entities, repositories etc. out of the bundle how do we work with forms and validation etc.? This is something I will look at over my next few posts.

Symfony2: Avoiding coupling applications to the environment

A quick post looking avoiding tying a Symfony2 application to the environment and instead varying config per environment.

It’s tempting to access the env parameter from within an application in order to make decisions. A very crude example designed to make what is happening clear, is getting the environment within from the container in a controller and using that to decide whether to actually send an SMS alert:

We could clean this up by moving the sending of the SMS into a service and injecting the value of the parameter into it:

with this service definition:

However whilst moving the sending of the SMS out of the controller is a good idea we are still coupling the service to the environment.

Why this is bad in theory

The application should not be aware of what environment it is running under, it should just be configured a particular way based on its configuration files. Since we want to vary configuration between different environments there are typically different configuration for each of these environments. We choose which of these sets of configuration to load rather than telling the application which environment it is in.

Why this is bad in practice

This is all very well in theory but what problem’s can it cause us to make the application aware of what environment it is in in practice? The first is that we can only change the configuration now in the code if we change our mind about the setting. The second is that we can no longer test the setting by changing config, we need to change the environment the application is running in altogether to see the effect, if we used a configuration value instead we could just change that for the current environment.

If you later need to add further environments which need the same change then you will need to add to the if statements in the code to do this. With a configuration value you can just set it for that environment if it needs a value different to the default configuration.

With a good parameter name it is then clear as to its purpose as well, so reconfiguring the application is straightforward without having to dig into the application code to see what the parameter is used for.

with this service definition:

and in the config_dev.yml file:

Rather than simply setting this as a parameter you could expose it as bundle configuration as I looked at in my previous post.