DEV Community

Discussion on: Why you desperately need Value Objects in your life

Collapse
peerreynders profile image
peerreynders • Edited on
  • Please use code blocks instead of inaccessible images—the font is too small.

  • Your regular expression '^[a-zA-Z ]$' only passes only on single character names. '^[a-zA-Z ]{2,100}$' takes care of everything.

But worry not, I’ve found an easy way to workaround it - all we need to do is to add a private property to the class.

Until your favourite duct tape programmer comes along.

class InvalidName extends Name {
  constructor(value: string) {
    super('Valid Name');
    this.value = value;
  }
}

sendBirthdayInvitation(
  new InvalidName('Inv@lid n@m3'),
  'someone@gmail.com',
  30
);
Enter fullscreen mode Exit fullscreen mode

😁

In TypeScript this is usually referred to as a type brand (nominal typing) and there is a way to be more sneaky about it:

// inside the `Name.ts` module
declare const validName: unique symbol;

// export type Name = …
type Name = string & {
  [validName]: true;
};

// export
function assertValidName(value: string): asserts value is Name {
  if (!/^[a-zA-Z ]*$/.test(value))
    throw new Error(
      `Invalid name ${value}, Should consist of Alphabetical characters and spaces only`
    );
  if (value.length < 2 || 100 < value.length)
    throw new Error(`Invalid name S{value}: Should be 2-100 characters long`);
}

/*
  Assertion Functions:
  https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions
*/

// In another module
const tryName = 'valid Name';
assertValidName(tryName);
sendBirthdayInvitation(tryName, 'someone@gmail.com', 30);
//
// works; `tryName` is now typed as
// const tryName: "valid Name" & {
//   [validName]: true;
// } 

const invalidName = 'Inv@lid n@m3';
assertValidName(invalidName); // Error: invalid name Inv@lid n@m3, Should consist of Alphabetical characters and spaces only
sendBirthdayInvitation(invalidName, 'someone@gmail.com', 30);

const anotherTry = 'Inv@lid n@m3';
sendBirthdayInvitation(anotherTry, 'someone@gmail.com', 30);
// Transpilation error:
// Argument of type 'string' is not assignable to parameter of type 'Name'.
// Type 'string' is not assignable to type '{ [validName]: true; }'.(2345)

function sendBirthdayInvitation(
  senderName: Name,
  recipient: string,
  age: number
) {
  console.log(senderName);
}
Enter fullscreen mode Exit fullscreen mode

playground
credit

The validName property only exists at compile time so there is no runtime overhead.

It is well worth looking beyond class/interface into type, intersection types, and discriminated unions.


James Coplien
"How many of you do object-oriented programming? What language? Java is the only language in which you cannot do object-oriented programming. Huh? Other languages. Smalltalk - no. C# - no. Python - no. No. Any JavaScript programmers here? Here are my object-oriented programmers - they're writing objects - the rest of you are writing classes".


The fundamental idea behind a value object is that its identity is established by the contained values rather than the reference of the actual object.

"With Value Objects, we establish identity through the structural equality of two instances."
Value Objects - DDD

Given that new Name('John Doe') !== new Name('John Doe') there really needs to be a comparison method (equals()) on the class that checks for structural equality or in the case of "objects as records" a comparison function.

JavaScript Tuples and Records (Stage 2, December 2021) may make that easier in the future.

Collapse
zoharp profile image
Zohar Pfeffer Author

Wow! it was worth putting the effort to write this article only to get this amazing feedback from you! Thank you for all this information.

Since this is an introductry article with the main goal of explaining the core principles of Value Objects, I prioritized understandability above all in my example implementation. So there are a few flaws in my implementation at the price of illustrating the concepts of Value Objects more clearly and keeping it readable for folks using other languages than TypeScript.

Have you ever written a project following Domain Driven Design with TypeScript? If so, please share with us how did you implement its principles? Did you use a dedicated library?

Sorry it was hard to read the code, I went with screenshots rather than code blocks because I wanted to show how the Value Object utilizes the IDE to make our lives much easier. Also, I thought it would be helpful if I highlight the last changes in each code snippet. Maybe I'll use a combination of code blocks and screenshots next time.

Yep, I got that Regex wrong 😅, I intentionally checked the length on a separate line for readability though.

An equals() method is definitely a must here. I did not want to go into equality comparison and how it diffrentiate between Value Objects and Entities since it's an introductory article.

Thank you for explaining type branding so clearly, I did come across the concept on my research but decided not to use it for the example in the article. If you use it you cannot use a base Value Object class though, how would you deal with that? Composition maybe?

Collapse
peerreynders profile image
peerreynders

illustrating the concepts of Value Objects more clearly and keeping it readable for folks using other languages than TypeScript.

It comes across as if you actually mean "class-based object oriented" languages rather than languages in general.

Domain-Driven Design: Tackling Complexity in the Heart of Software was published in 2003 and was a product of its time. Java was dogmatically object-oriented but "values" where needed, so Eric Evans conceived the "Value Object". However now the broader spectrum of languages aren't constrained to objects. And neither is DDD constrained to OO.

While classes are types, not all types are classes. Many languages support tuples and records in such a way that they can serve as "Value Objects" even though they're not objects.

"TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed, TypeScript’s type system has evolved to model code written by native JavaScripters. The resulting system is powerful, interesting and messy."
ref

So even TypeScript had to adapt. In JavaScript arrays often serve as tuples and plain objects as records or dictionaries; they are treated as values even though they have the runtime semantics of objects (not necessarily created by a class).

Have you ever written a project following Domain Driven Design with TypeScript?

No, likely because most TypeScript based projects still embrace the Smart UI philosophy as it fits the team's capabilities better.

Did you use a dedicated library?

DDD is an approach, a way of thinking and working, not a software tool. And in my experience frameworks often tend to make it harder to implement DDD because they prioritize the needs of the framework. I would even go as far as saying that tooling is largely responsible for the dominance of the Smart UI approach.

Now perhaps there may be domain specific libraries out there but that means

  • that someone already has done the hard work for you
  • you are locked into that library's opinion and may only discover much later that it isn't a good fit or prevents you from evolving into the direction that you need to go.

Bring Clarity To Your Monolith with Bounded Contexts (Rails)

Maybe I'll use a combination of code blocks and screenshots next time.

I'd favor code blocks and use images only when absolutely necessary (perhaps augmented with links to something like codesandbox) Typically just code blocks will do because with images you should be supplying the alt text anyway.

I did not want to go into equality comparison and how it differentiate between Value Objects and Entities since it's an introductory article.

Structural equality is a key characteristic of values and value objects; so an introduction without that leaves the audience largely in the dark of the value that value objects bring.

If you use it you cannot use a base Value Object class though, how would you deal with that? Composition maybe?

I'm not sure I understand the question. I would need some convincing that using inheritance with value objects is even a good idea. And the technique you applied falls within the practice "type branding".