DDD – Understating Value Objects with Swift

This article is part of mini series about Domain Driven Design. And in this week we will continue with one of its primary concepts – Value Objects and Entity Objects. In this one I will present you Value Objects.

Easiest way to understand Value Objects is by understanding how it’s different from Entity Object.

Core difference is how we define identity between two Value Objects and how we define it between two Entity Objects. Understanding the difference will help you recognise value and entity objects in your system.

Entity Object Identity

We use Entity Objects when we are modelling domain concept where we interested in models identity to distinguish that Entity Object from other instances of the object.

Simple example of an EntityObject would look something like this:

struct EntityObject<T>: Equatable {
	let id: UUID
	let value: T
	
	init(id: UUID = UUID(), value: T) {
		self.id = id
		self.value = value
	}
	
	static func == (lhs: EntityObject, rhs: EntityObject) -> Bool {
		return lhs.id == rhs.id
	}
}

In example above object identity is defined using randomly generated UUID. To guarantee that identity is decided base on ‘id’, I suggest that you use and add custom implementation of ‘Equatable’ protocol.

Making things more clearer let’s have a look at two examples and see how does this work in action?

let entity1 = EntityObject(id: UUID(), value: "Value")
let entity2 = EntityObject(id: UUID(), value: "Value")
print(entity1 == entity2) /* False */
let id = UUID()
let entity1 = EntityObject(id: id, value: "Value")
let entity2 = EntityObject(id: id, value: "AnotherValue")
print(entity1 == entity2) /* True */
let id = UUID()
let entity1 = EntityObject(id: id, value: "Value")
let entity2 = EntityObject(id: id, value: "Value")
print(entity1 == entity2) /* True */

Take a look at examples above and you will see that in first one, even though that we created two instances with same value, instances are not equal.

Opposite to first example, second and third proves that only equal object identity (in our case ‘id’) defines same objects.

Value Object

Now, since we understand Entity Models, let’s dive in into Value Objects.

Value Object Identity

Opposite to Entity Objects, we define identity through structural equality of two instances – two objects have the same content.

For simplest example we can check how works comparing ‘String’ values.

let name = "name"
let anotherName = "another name"
let thirdName = "name"
print(name == anotherName) /* False */
print(name == thirdName) /* True */
print(anotherName == thirdName) /* False */

But since in our daily lives we are dealing with more than strings, let’s take at more concrete scenario.

struct ValueObject<T: Equatable, Z: Equatable> {
	let contentT: T
	let contentZ: Z
	
	init(contentT: T, contentZ: Z) {
		self.contentT = contentT
		self.contentZ = contentZ
	}
	
	func isEqual(against object: ValueObject) -> Bool {
		return
			self.contentT == object.contentT &&
			self.contentZ == object.contentZ
	}
}

As mentioned before, to identify two value objects we examine contents of the objects. Implantation of ‘isEqual’ shows that we compare contents of self against other instance of value object.

In Swift you can use ‘Equatable’ protocol and eliminate implementation of manual comparison. I have made example using generics to really point out what is important.

struct ValueObject<T: Equatable, Z: Equatable>: Equatable {
	let contentT: T
	let contentZ: Z
	
	init(contentT: T, contentZ: Z) {
		self.contentT = contentT
		self.contentZ = contentZ
	}
}
Value Object Usage Example

One of powerful usages for Value Objects is when we are modelling our Domain Objects and we want to encapsulate domain logic even more.

In previous post we have have remodelled our User object to this state:

struct User {
	let id: UUID
	private(set) var name: String
	private(set) var lastName: String
	let email: String
	
	init(id: UUID, name: String, lastName: String, email: String) throws {
		try UserDataValidator.validate(name, lastName, email)
		self.id = id
		self.name = name
		self.lastName = lastName
		self.email = email
	}
	
	mutating func edit(name: String) throws {
		try UserDataValidator.validate(name)
		self.name = name
	}
	
	mutating func edit(lastName: String) throws {
		try UserDataValidator.validate(lastName)
		self.lastName = lastName
	}
}

We can see that most of validation in implemented inside ‘UserDataValidator’. Although this works fine for now, we can do it even better. When project grows more and more business logic is introduced into our system. Resulting into more and more complex domain models which become harder and harder to maintain and test as well.

If we want to keep our system under control, it is good idea to keep things in isolation.

In our User model, validation of its properties is happening inside of it. We will isolate validation of values – resulting into more isolated system.

To isolate ‘name’ and its validation logic, we would need to introduce ‘UserName’ struct for example.

struct UserName {
	let name: String
	
	init(_ name: String)throws {
		try UserDataValidator.validate(name)
		self.name = name
	}
}

And to do same with ‘lastName’ we would introduce ‘UserLastName’.

struct UserLastName {
	let lastName: String
	
	init(_ lastName: String)throws {
		try UserDataValidator.validate(lastName)
		self.lastName = lastName
	}
}

But looking and this two example, it is easy to notice, that we are repeating ourselves. To follow ‘DRY’ principle we can eliminate this repetition with power of Swift’s generics and deliver more generic solution.

If we inject validation code, string can become generic parameter, resulting into generic solution – ‘ValidatedValue’ object. ValidatedValue object will help us encapsulating validation of value objects upon editing or creation. It is designed to allows us to inject validator upon creation or using property injection, resulting into more flexible component.

struct ValidatedValue<T: Equatable>: Equatable {
	typealias Validator = (T) throws -> Void
	
	var validator: Validator = { _ in }
	private(set) var value: T
	
	init(_ value: T) {
		self.value = value
	}
	
	init(_ value: T, validator: @escaping Validator) throws {
		try validator(value)
		self.init(value)
		self.validator = validator
	}
	
	mutating func edit(_ value: T) throws {
		try validator(value)
		self.value = value
	}
	
	static func == (lhs: ValidatedValue<T>, rhs: ValidatedValue<T>) -> Bool {
		return lhs.value == rhs.value
	}
}

Since ‘Validator’ is not equatable, Swift forces us to implement manual conformance to ‘Equatable’ protocol.

Now let’s remodel our ‘User’ object and put ‘ValidatedValue’ into usage.

struct User {
	typealias Name = ValidatedValue<String>
	typealias LastName = ValidatedValue<String>
	
	let id: UUID
	private(set) var name = Name("")
	private(set) var lastName = LastName("")
	let email: String
	
    /* To prevent any model invariants we introduced 'factory' for our User model */
	static func make(id: UUID, name: String, lastName: String, email: String) throws -> User {
		let name = try Name(name, validator: UserDataValidator.validate)
		let lastName = try LastName(lastName, validator: UserDataValidator.validate)
		let email = try UserDataValidator.validate(email)
		return User(id: id, name: name, lastName: lastName, email: email)
	}
	
	private init(id: UUID, name: Name, lastName: LastName, email: String) {
		self.id = id
		self.name = name
		self.lastName = lastName
		self.email = email
	}
	
	mutating func edit(name: String) throws {
		try self.name.edit(name)
	}

	mutating func edit(lastName: String) throws {
		try self.lastName.edit(lastName)
	}
}

To guarantee that our User model’s validated properties have validated values upon creation and to be protected against invariants, we used static ‘make’ method – so called ‘factory’.

Learnings and result

We have learnt core difference between Value and Entity Objects and showed how can Value Objects be used in domain modelling. There are tones of other usages as well, but this was not primary goal of this post.

As a result we have optimised our User model even more and prepare it for further modelling.

If you missed my previous post about anemic and rich domain models, I suggest that you read it too.

Thank you for reading!

In case of any questions or comments feel free to contact me or leave it comments section bellow.


Architectural Patterns – Decorator

/

In this article I will present you powerful architectural pattern that I have been using it a lot called Decorator.

1. What is Decorator

...

Detect user’s country without accessing their location

/

Sometimes we are faced with challenge where we would like to improve user experience of our app based on where in the world or better said in which country ...

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments