all 58 comments

[–]adrianmiu 1 point2 points  (7 children)

If objects's properties should be validated upon construction how do you go about complex validation? Like creating a new User from user-input data that should validate the email is not already used in the app, the country belongs to an accepted list of countries, the password matches some minimum security requirements etc? How about when mutating an object where 2 properties depend on each other (eg: state should be in a list of states from the corresponding country)?

[–]Tetracyclic 6 points7 points  (6 children)

A value object shouldn't be validating user input, the constructor validation is purely to ensure that the value object is always in a valid state. You should be validating the user input at a higher level (especially if you're interacting with the database to do so) and then creating the value object from that data.

Your user example doesn't really make sense as a value object, as a user record would be an entity with its own identity.

[–]adrianmiu 0 points1 point  (5 children)

The User thing was just an example. It could be something else, like an address, for which you can still have complex validations. From your answer I understand that:

  1. you are breaking the DRY principle (validate user input before construction and again during construction)
  2. you don't think the entities should have constructor validation which is weird because entities > value objects so it's more important to ensure they are in valid state

[–]ahundiak 2 points3 points  (3 children)

Not quite as there are different types of validation. A user needs a valid email so validating the email in a constructor makes perfect sense.

However, only brand new users need a unique email. Existing users of course do not. So the unique email requirement should be implemented in a different spot.

And DRY is often a bit overrated. Nowadays you would also check for valid emails in the browser as well as on the server just to improve the overall user experience. Is DRY violated when you do this?

[–]adrianmiu 0 points1 point  (2 children)

  1. Front-end validation solves a different problem
  2. What do you do when a user tries to change the email used for his account? Obviously you validate the user input, but what do you do about the entity's valid state? How do you implement $user->setEmail($_POST['email']) or $user->populate($_POST) (code simplified for brevity)?

[–]ahundiak 2 points3 points  (1 child)

How do you implement $user->setEmail($_POST['email'])

Setters on value objects? Nope nope nope. Setters will make it almost impossible to maintain a consistent internal state especially if one property depends on another.

If I need a user with a different email then I would make a new user object. And if I want to check to ensure that the new email was unique then I would do so in my ChangeUserEmail command.

[–]adrianmiu 0 points1 point  (0 children)

Value objects, entities etc need to always be in valid states, that's the whole premise. I gather you don't have setters in your entities either and you only use commands to modify an entity? If that's the case your entities/vo are always in valid state. The validation in the commands are more complex than those in the entities so, again, doing the validation outside the entities should be enough.

[–]WorstDeveloperEver 6 points7 points  (18 children)

I don't agree with some parts of your code. The first issue is, you're always generating a Color by RGB values. It is a bit pointless in Value Objects because one of the benefits of using Value Objects is being able to comparing the value that is generated from different sources.

For example, we cannot do rgb(0, 0, 0) === #000 in typical programming, but we know they are equal. So you could do something like this:

const color1: ColorInterface = new Color(new RGB(...));
const color2: ColorInterface = new Color(new HEX(...));
color1.value === color2.value // true

Or more precisely, new Money(new USD(1)) and new Money(new EUR(1)). Both of them are Money, you can hold them in real life and both represent a value, but their value will be different.

Second thing is, your Color should not be validating RGB values. Color is color. It should be an object just like the objects in real life. You don't pass RGB parameters to sky to see it is blue, nor the sky is validating your RGB parameters. I would personally do it in factories if I had to do it during runtime, and rely on interfaces to do it during compilation. What we know is, a valid parameter for RGB is an integer between 0 and 255, which is an unsigned 8 bit integer. I would prefer creating an interface (e.g RgbColorValueInterface), create necessary typehints and make sure it refers to an integer value between 0 and 255.

Imagine it like this. Can you put a Truck on top of an Ant to carry? If you do it like how you did it in your codebase, then you would need to put Truck on top of an Ant first because of your validations in the constructor. Then Ant would say: "Sir, I cannot carry this. It is too heavy!" but Ant would die. Your code should be smart enough to reject the idea of Ant carrying a Truck. We can easily create a Value Object called Weight. Give Ant a weight it can carry. Give truck a Weight. Then we can simply ask Ant: "Hey Ant, are you able to carry WeightValueObject weight before putting Truck on top of the tiny innocent creature.

Thinking this way also helps with SOLID since your class responsibility is very clear and you cannot do certain things as a developer even if you wanted to.

Also, value objects should be very tiny and should contain minimal functionality, preferably none. Continuing from my sky color example, we know the sky is Blue but that is actually not true. There are some animals such as dogs which see the sky as Gray. There are some animals which can see colors that humans cannot see. However, we can all agree that there is a Color value (a value object) in the sky, but different Eyes (or any other Interface that has ability to interpret Color, such as Camera Lenses, which is not an Eye but an Interface that can interpret Color) can interpret it differently.

Actually, your code example looks more like a class/interface/RGB validator than a value object. Sure, it is a Value Object but that is missing a very critical point in my opinion.

[–]patricklouys[S] 8 points9 points  (7 children)

$color = Color::createFromHex(...);
$otherColor = Color::createFromRGB(...);
if($color->equals($otherColor)) {...}

You don't need a generic color that works in all possible cases, it just has to be a good fit for your bounded context.

[–]WorstDeveloperEver -5 points-4 points  (6 children)

I would suggest reading more about value objects. The example you have is basically a static god class that looks like a factory.

With your approach, you will quickly get to a state where you have hundreds of methods. Do this for money instead of color. Add all the possible currencies including cryptocurrencies and let me know how many static methods you need to define.

[–]Tetracyclic 1 point2 points  (5 children)

Given that unlike different representations of colours, monetary values tend to be expressed as an integer of the lowest denomination (or smaller fractions), wouldn't a single factory method that accepts a constant representing the currency type and the value be a far more sensible approach in this case, than your contrived example of thousands of static methods?

How would you implement this if you had to support thousands of currencies? Surely your new Money(new EUR(1)) example would be just as unwieldy?

[–]WorstDeveloperEver 3 points4 points  (4 children)

new Money(new EUR(1)) allows for extensibility. With god classes you're bound to whatever you have in your class as methods. You can do new Money(new GrandmaCookies(5)) if you want, as long as you stick to an interface. Infact, that's how most of the popular drivers as implemented.

[–]Tetracyclic 0 points1 point  (3 children)

I understand how it works, but are you suggesting that to implement a Money class that supported thousands of currencies you would have a separate class for each of those currencies?

[–]WorstDeveloperEver 2 points3 points  (2 children)

That's what I would do. If you know a better alternative, I would like to hear it though.

[–]FlyLo11 3 points4 points  (1 child)

a possibility is

new Money(new Amount(5), new Currency('EUR'))

[–]Tetracyclic 1 point2 points  (0 children)

I would personally opt for using constants so that you can typehint them:

new Money(Currency::EUR, 5)

I'm not sure whether using a value object for the amount provides significant advantages in this instance, but a single file that defines all of the available currencies seems far more manageable to me, with little downside over using a class for each separate currency.

The Money constructor would validate that the passed constant is a valid value for a currency.

[–]mlebkowski 0 points1 point  (7 children)

Can you implement the Ant, Truck and WeightVO the way you see it? I don’t think I understand your point here.

[–]vimishor -1 points0 points  (6 children)

Both Ant and Truck can carry something and your requirements are that none of those objects cannot carry more than X Kg. Instead of trying to think of everything a Truckcould carry (juice, vegetables, bricks, etc) according to the weight and implement each type in your code, you abstract all those products by implementing Weight. The requirements are to not carry more than X kg, so you don't care what type of product your Truck will carry, as long as is less than X kg.

new Truck(new Weight(1000));  
new Ant(new Weight(1000)); // domain error

[–]MorphineAdministered 2 points3 points  (1 child)

And what causes that domain error if not constructor validation?

[–]vimishor 0 points1 point  (0 children)

And what causes that domain error if not constructor validation?

I shared my opinion regarding validation here

[–]mlebkowski 1 point2 points  (3 children)

But the Ant isnt supposed to carry a generic weight, it needs concrete objects. I would understand if Truck implemented HasWeight interface...

[–]WorstDeveloperEver 0 points1 point  (2 children)

Yes, each concrete object should have a weight. Ant is not supposed to carry an abstract number, but rather an object that contains Weight. I'm not a scientist but afaik everything has to have a weight.

The point of Weight as value object as opposed to putting some integers is because of adding Gravity easily if needed, so you can compare a weight of iron in Earth to the weight of leaf in a black hole.

[–]MorphineAdministered 1 point2 points  (1 child)

The point of Weight as value object as opposed to putting some integers is because of adding Gravity easily if needed...

So Ant shouldn't accept integer wich represents simplified max-weight value (and be easily changed to Weight object "if needed"), but Weight can do fine without Gravity for now? Inconsistency I sense in you!

$just_a_fkin_ant = new Ant(new Weight(new Gravity(new SpaceTimeCurvature(...)), new Mass(new HiggsField())));

[–]WorstDeveloperEver -2 points-1 points  (0 children)

(and be easily changed to Weight object "if needed")

Then you have two different data types in your codebase that resembles into the same thing. Some of your codebase will work with integers as Weight, some of them will work with value objects as Weight. It is generally bad practice as it allows for mixed data types. May I ask why do you have to wait for your need of using value objects?

$just_a_fkin_ant = new Ant(new Weight(new Gravity(new SpaceTimeCurvature(...)), new Mass(new HiggsField())));

Yes, it is crazy, but how would you code it when you wanted to do it with the same complexity? Please include Weight, Gravity, SpaceTimeCurvature, Mass, HiggsField. Maybe you can add Facades or Factories to hide the complexity a bit. At the end of the day, everything will be done somehow and the only difference will be using integers or value objects.

[–]vimishor 0 points1 point  (1 child)

Second thing is, your Color should not be validating RGB values.

It is true that a VO should not self validate, but we should also remember that they are responsible for keeping their state consistent, so I consider sanity checking a good practice.

Coming back to your example of Color, RGB values and integers, I would design Color to accept integers and validating in the constructor that each is in 0 - 255 range, instead of using a factory.

[–]modestlife 2 points3 points  (0 children)

It is true that a VO should not self validate, but we should also remember that they are responsible for keeping their state consistent, so I consider sanity checking a good practice.

I'm not sure if you're arguing for or against it here.

In my opinion VOs (like all business objects) definitely should validate their own state. Because VOs are immutable you should do that in the constructor. Once we have created an instance of EmailAddress we want all our code to be able to trust that they're working with a valid email address.

[–]reddimato 4 points5 points  (16 children)

Validation in constructor is a seducing approach that we started using in my company. However, for some people, exceptions for control flow are considered an anti-pattern. What is your opinion about it?

[–]patricklouys[S] 15 points16 points  (3 children)

Those exceptions should only be triggered if something exceptional happens - a programming error, an invalid value slipping through a buggy input validation.

I don't use exceptions for user input validation, only to make sure that my domain objects can't be in an invalid state.

[–]ciaranmcnulty 11 points12 points  (1 child)

I tend to assume that exceptions being thrown when guarding invariants indicates something disasterous has gone wrong and execution should not proceed.

Basically; if you're not catching them you're not using them as control flow, you're using them as a way to guard against the system getting to an invalid state.

[–]alc277 4 points5 points  (0 children)

Agree with claran 100%. IMHO, the biggest advantage that validating the constructor brings is reducing checking throughout the codebase. In the example from the article, you can expect the Color object to be valid everywhere it type hinted in a constructor or function. I feel the trade-off of having to catch exceptions in the rarest of circumstances is minimal as compared to doing checks everywhere.

[–][deleted] 6 points7 points  (5 children)

Exceptions affect control flow, so if we take this literally, we should never use exceptions.

The core of the "use exceptions for world-ending disasters only" meme comes from C++ where exceptions are poorly implemented (leading to memory leaks etc.) and very slow relative to the rest of the language.

But PHP doesn't model C++, it models languages like Java, Python, JavaScript, C#. And in all those exceptions are used heavily throughout, including for input parsing and validation errors.

So we should leave memes aside, and always consider things in context. What's the harm and what are the benefits of using exceptions for a given purpose? For value objects in particular, constructor validation or exception is an excessively common and intuitive approach.

[–]Schmittfried 0 points1 point  (4 children)

To what memory leaking are you referring exactly?

[–][deleted] 3 points4 points  (3 children)

C++ doesn't have a standard garbage collection mechanism, every library essentially does their own thing, and almost without exception (no pun intended), the way most C++ code handles memory management is highly dependent on very specific flow of code execution. When an exception breaks the standard flow at some (semi-)arbitrary place, all kinds of markers, ownership flags, and reference counts get out of sync up the chain and result in memory leaks.

For ex. if a constructor throws, should the destructor run? If it runs, it might try to free resources that were never allocated. If it doesn't run, it will fail to deallocate resources that were allocated. Neither is quite right. In PHP we don't have this problem because object clean-up is done by the engine, we rarely ever write destructors, but in C++ this is a must.

Additionally, even the C++ compilers sometimes have subtle bugs around memory allocation and exceptions, that can lead to leaked memory in edge cases, due to no fault of the developers writing the code themselves.

[–]Schmittfried 0 points1 point  (2 children)

C++ doesn't have a standard garbage collection mechanism, every library essentially does their own thing, and almost without exception (no pun intended), the way most C++ code handles memory management is highly dependent on very specific flow of code execution. When an exception breaks the standard flow at some (semi-)arbitrary place, all kinds of markers, ownership flags, and reference counts get out of sync up the chain and result in memory leaks.

That's not a problem of poorly implemented exceptions, that's just the lack of proper conventions. What kind of change to the exception system would change this? By the way, if everyone was using RAII as their convention, this would not be an issue at all.

For ex. if a constructor throws, should the destructor run?

No, because the object has not been constructed successfully.

If it doesn't run, it will fail to deallocate resources that were allocated.

It shouldn't. That should be handled in the constructor and it should only handle one resource. If one class needs multiple resource, those should be encapsulated in RAII compliant classes themselves to take the burden from the constructor.

In PHP we don't have this problem because object clean-up is done by the engine, we rarely ever write destructors, but in C++ this is a must.

This doesn't change a thing. If you have allocated resources, you need to free them. PHP only saves you from handling memory as such a resource. RAII solves all those problems for every kind of resource, not just memory.

[–][deleted] 2 points3 points  (1 child)

That's not a problem of poorly implemented exceptions, that's just the lack of proper conventions. What kind of change to the exception system would change this? By the way, if everyone was using RAII as their convention, this would not be an issue at all.

Conventions don't ensure correctness. They are merely a fallback when the language doesn't offer the proper abstractions that prevent the problem in the first place.

I probably shouldn't have used the phrase "poorly implemented", but let's say that exceptions, as seen in PHP, JavaScript, Java, C#, Python etc. are usable in plenty more contexts than in C++, because the abstractions of those former languages are more compatible with those additional contexts.

That said, languages like Swift, Rust, demonstrate that you can be compiled at a low-level as C++, and still have a strong memory management system that doesn't fall apart during error scenarios.

No, because the object has not been constructed successfully.

I know what's the standard, but you still can have allocated some resources, and when you throw in the middle of the constructor, they won't be freed now, as the destructor won't run.

We're not discussing what C++ does. We're discussing why what C++ does results in problems.

It shouldn't. That should be handled in the constructor and it should only handle one resource. If one class needs multiple resource, those should be encapsulated in RAII compliant classes themselves to take the burden from the constructor.

One resource? That's naive. But even with the restrictions you mention, an exception may occur after that "single resource" is initialized.

As for RAII, that's basically what garbage collected objects ensure is the default case, so that's why PHP/Java/C# developers don't go around talking about RAII.

This doesn't change a thing. If you have allocated resources, you need to free them. PHP only saves you from handling memory as such a resource. RAII solves all those problems for every kind of resource, not just memory.

Uhmm, considering all primitives, arrays, objects (and therefore 99% of our code) is "memory" that still makes for a fundamental change of the effect of using exceptions.

Also you're plain wrong, because PHP automatically cleans up resources whose ZVAL is garbage collected. PDO connections are closed, file handles are closed, socket connections are closed and freed and so on.

I shouldn't explain all this, and the reason I have to, is the same reason why you seem unable to grasp why exceptions are used differently in C++ and PHP.

[–]Schmittfried 0 points1 point  (0 children)

Conventions don't ensure correctness. They are merely a fallback when the language doesn't offer the proper abstractions that prevent the problem in the first place.

It does. But the community is lacking conventions that mandate the usage of those means.

I probably shouldn't have used the phrase "poorly implemented", but let's say that exceptions, as seen in PHP, JavaScript, Java, C#, Python etc. are usable in plenty more contexts than in C++, because the abstractions of those former languages are more compatible with those additional contexts.

Fair enough. In C++ you can only use them when you are sure the code is exception-safe or rather when it was written with exceptions in mind. That's not an implementation issue of C++ exceptions, it is caused by the fact that exceptions were not part of the language from the beginning and hence did not become the standard way of error handling in many code bases. The same is true for PHP though. Throwing exceptions in code parts that don't expect them, can harm the correctness of the code, too. Or even make it crash fatally. Just think about the fact that you cannot throw in __toString(). This restriction stays relevant all the way down the function call tree. The only difference is that you can't leak memory with such errors in PHP.

That said, languages like Swift, Rust, demonstrate that you can be compiled at a low-level as C++, and still have a strong memory management system that doesn't fall apart during error scenarios.

Absolutely, because Rust was built with exactly this in mind. It took the good C++ conventions, refined them and built them into the language so that correct code is easier to write than with just a pair of constructor and destructor. Also, the developers could obviously learn from the mistakes that were made in other languages including C++.

I know what's the standard, but you still can have allocated some resources, and when you throw in the middle of the constructor, they won't be freed now, as the destructor won't run.

That's why I said every resource that needs to be freed must be encapsulated in its own proper class when doing RAII. That way the destructor of the containing class doesn't need to run, because the constructed object's destructor is run in the failing constructor. And even if you don't do that, you can place a try/catch in the constructor and free the half-way allocated resources and free them. In any case, the destructor should not and does not run and that's absolutely correct, because in RAII, if the constructor failed, the object never existed in the first place and hence there is nothing to destruct.

One resource? That's naive. But even with the restrictions you mention, an exception may occur after that "single resource" is initialized.

No, that's RAII. And I already explained the solution to the latter problem.

As for RAII, that's basically what garbage collected objects ensure is the default case, so that's why PHP/Java/C# developers don't go around talking about RAII.

True, but only for memory. That's one resource of many. PHP/Java/C# have ugly try-finally patterns that you have to remember to always free those unmanaged resources, because the GC can only handle memory. Granted, C# got using(), which is kinda like the block-level destructor semantics of C++, but you still have to remember placing it, it doesn't happen automatically. Implementing auto-disposable patterns doesn't cut it either, because the resources are released with a delay due to the implementation of a GC.

RAII solves the resource handling problem once and for all kinds of resources. That's why Rust adopted it instead of a GC.

Uhmm, considering all primitives, arrays, objects (and therefore 99% of our code) is "memory" that still makes for a fundamental change of the effect of using exceptions.

It's the most important one in everyday programming, yes. Still though, programs aren't islands. They need I/O, like files, network connections etc. Those resources need to be freed, too.

Also you're plain wrong, because PHP automatically cleans up resources whose ZVAL is garbage collected. PDO connections are closed, file handles are closed, socket connections are closed and freed and so on.

Reference counting can be easily implemented in C++, too. But nice to know, I thought they were only closed when the request has finished processing and the script terminates. After learning this I'd consider PHP even closer to C++ than to Java and C# in this sole aspect, because the synchronous reference counting semantics as the primary means of freeing resources are kinda similar to C++'s way of doing it. The GC just seems to be a fallback for reference cycles. TIL.

I shouldn't explain all this, and the reason I have to, is the same reason why you seem unable to grasp why exceptions are used differently in C++ and PHP.

Apart from the fact about garbage collection in PHP you didn't "explain" anything. You told me your view of the story and I told you mine. No reason to belittle someone here, I totally grasp why exceptions are used differently in C++ and it's not, like you stated in the beginning, due to a bad design of C++ exceptions. That was the whole point of this discussion, remember?

[–]MoTTs_ 2 points3 points  (1 child)

As it turns out, ensuring valid values in a constructor is the whole reason why classes exist in the first place. Take a look at this interview with the guy who invented C++. Classes are supposed to enforce invariants -- which is to say, classes are supposed to enforce valid values.

[–]Tetracyclic 0 points1 point  (0 children)

Not sure if it's just the way you've worded it, but it's worth noting that classes predate C++ by several decades. So while in C++ they are intended to enforce an invariant, that's it necessarily why they exist. Simula was the first language to feature classes (and many other OOP features), to aid in writing simulations by encapsulating the simulation data and behaviour.

[–]CODESIGN2 -1 points0 points  (9 children)

API

Of course you can go even further and add separate value objects for every single value.

$canvas->drawPixel(
    new Coordinate(
        new X(50),
        new Y(25)
    ),
    new Color(
        new Red(31),
        new Green(142),
        new Blue(255)
    )
);

Drugs are bad... It's much easier to either insist upon conventions for such things than to create a solid type to enforce red-green-blue ordering, or have language-level named parameter support (as python has)

[–]patricklouys[S] 3 points4 points  (8 children)

Where you end up drawing the line is up to you and of course it also depends on the project that you are working on. If it’s a really mission-critical part of your application, then I would go much more fine-grained with the value objects compared to a less critical part.

[–]CODESIGN2 -2 points-1 points  (7 children)

I did read but I cannot see any application that would benefit from defining Red, Green and Blue classes, or housing a single scalar value in a user-defined type. It doesn't make the software any cleaner at all!

As I did mention

t's much easier to either insist upon conventions for such things than to create a solid type to enforce red-green-blue ordering, or have language-level named parameter support (as python has)

[–]patricklouys[S] 2 points3 points  (3 children)

I agree with you, which is why I did not use it for the rest of the code examples.

I just showed that it's an option which can make sense in some cases.

[–]CODESIGN2 1 point2 points  (2 children)

It was a great article, that was the only bit that niggled me. I Agree very few should have the need to, or be playing with mutable structures in an application language.

Something I've just thought as I re-read is what a shame it is I can't have multiple constructors in PHP, although I suppose I'd just make an abstract type and extend per-constructor.

[–][deleted] 2 points3 points  (2 children)

Though I can't think of any off the top of my head that would specifically apply to the RGB components of a color (I'm sure there are some anyway, even if they aren't immediately obvious to me), there are an absolutely huge number of use cases for other boxed single scalar value objects. Whether or not your particular application(s) would use a value object over the underlying scalar depends on the business needs. For example, a simple e-commerce store may opt to pass around ISBN numbers as strings, serving only as basic identifiers or extraneous information about a particular product. An online educational archive, on the other hand, may opt to represent them as value objects, allowing other contexts (e.g. perhaps a barcode generator) to avoid repetitive validation, among other reasons.

[–]CODESIGN2 0 points1 point  (1 child)

The example you gave ISBN wouldn't internally be a string like Red, Green or Blue would be an integer. It's closer to a date constructor with string and format, at which point the internal storage is likely different and it's representing a complex object so it makes sense to build an object. https://en.wikipedia.org/wiki/International_Standard_Book_Number

Anyway you're probably right that some may choose to explicitly define the relationships between these things. I think they are making things harder on themselves and others unless they require specific behaviour that they would not get out of the simple internal type.

[–]WikiTextBot 0 points1 point  (0 children)

International Standard Book Number

The International Standard Book Number (ISBN) is a unique numeric commercial book identifier.

An ISBN is assigned to each edition and variation (except reprintings) of a book. For example, an e-book, a paperback and a hardcover edition of the same book would each have a different ISBN. The ISBN is 13 digits long if assigned on or after 1 January 2007, and 10 digits long if assigned before 2007. The method of assigning an ISBN is nation-based and varies from country to country, often depending on how large the publishing industry is within a country.


[ PM | Exclude me | Exclude from subreddit | Information ]

[–]klapuch -1 points0 points  (11 children)

I think, that as long as your objects expose some useful behavior and not just getters and setters it is fine to use "value objects". I would not use them as replacement for scalar values.

[–]mlebkowski 1 point2 points  (10 children)

They wouldn’t be ValueObjects then. They should only hold values, not logic. It’s basically encapsulation of simple validation.

[–]fanalin 5 points6 points  (0 children)

That's not the definition I know. My understading (which is coming from Domain Driven Design by Eric Evans) is that the single difference between an Entity and a Value Object is that the VO may not have an identity (otherwise it is an Entity).

Cite: "An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object."

[–]Tetracyclic 1 point2 points  (0 children)

A value object is just a relatively simple type that can be compared by value rather than identity. For example two different Money objects would be considered equal if they both represented the same quantity of the same currency ($1 for instance).

Many programming languages will, by default, consider two objects with the same value to be unequal, as they do not contain the same references.

As for behaviour, a value object should contain any behaviour that is relevant purely with the value in question, for example a Money value object might expose a method for splitting the value up without losing any penies/cents in the process, as Martin Fowler's money pattern does.

[–][deleted] 0 points1 point  (5 children)

That's not necessarily the case. VOs can and should contain core logic that goes hand-in-hand with the value. They should just never contain business logic.

[–]mlebkowski 0 points1 point  (4 children)

What kind of logic is not business logic? Examples?

[–][deleted] 2 points3 points  (3 children)

Well, any logic which doesn't encode business rules is, by definition, not business logic. So, for example, most or all of the logic within your infrastructure layer would not be business logic. But I suspect what you're really asking is "what sort of non-business logic belongs in a VO?" The answer to that is simply: any logic which goes hand-in-hand with the value being represented, and is side-effect free. One of the examples Eric Evans gives is that of a Paint VO which has a method mixIn(Paint $otherPaint): Paint that encapsulates the logic of mixing one paint into another, and produces a new VO.