When I'm researching Domain Driven Design, I sometimes see stories of creating ValueObjects in all columns of a database, or creating ValueObjects that have no methods just by entering values. I think this is the result of being overly conscious of the definition of ValueObject, which is "immutable, exchangeable, and equivalent as a value." Aside from that definition, I'd like to say in this entry that the idea of "Isn't there a convenient value if there is a method?" Can find the ValueObject that you really need.
Before we get into the main subject, it's easier to find a ValueObject if you know what methods are useful, so let's talk about that. There are four Value Object patterns I'm finding.
Create a judgment method by wrapping the code value etc.
Code example [^ 1] [^ 2] that uses HTTP status code as a subject and makes it a Value Object.
[^ 1]: Since it is a sample code, it is a rough status code judgment.
[^ 2]: I use attr_accessor, but if you are worried that immutability will be impaired, you can make it private or change it to an instance variable reference.
http_status_code_value_object.rb
class HttpStatusCodeValueObject
attr_accessor :status_code
def initialize(status_code)
self.status_code = status_code
end
#2xx Success Is it a success status code?
def success?
200 <= status_code && status_code < 300
end
end
I think that Ruby and Rails often do it by extending standard types. You can write value% 2 == 1
asvalue.odd?
.
Assuming that you are creating an EC site, the estimated delivery date is calculated as order date + work time to ship + delivery time from the warehouse to the customer
. However, there is a range of "working time to ship" and "delivery time from warehouse to customer", so the code looks like this.
order_date = Date.parse('2020-01-01')
work_days = 0..1 #Working time before shipping is within 1 day
delivery_days = 1..2 #Delivery time from warehouse to customer is 1-2 days
#I'm happy to write this, but I get an error
#Order date+Working time to ship+Delivery time from warehouse to customer
# order_date + work_days + delivery_days
#Severe reality
delivery_date_from = order_date
delivery_date_to = order_date
delivery_date_from += work_days.first
delivery_date_to += work_days.last
delivery_date_from += delivery_days.first
delivery_date_to += delivery_days.last
So, create a class that can calculate the date range as ValueObject.
date_range_value_object.rb
class DateRangeValueObject
attr_accessor :from, :to
def initialize(from, to = nil)
self.from = from
self.to = to || from
end
#Only addition is defined because no other operator is required
def +(other)
other_from = other
other_to = other
if other.is_a?(Range)
other_from = other.first
other_to = other.last
end
self.class.new(from + other_from, to + other_to)
end
#Export to primitive values
def to_range
from..to
end
end
This class makes it easier to write calculations.
value_object = DateRangeValueObject.new(order_date)
value_object += work_days
value_object += delivery_days
value_object.to_range
I think this is also something that Ruby and Rails often do by extending standard types.
It's common to check the value and throw an exception if a strange value comes in. Sometimes you can return the result of a failure, if not until you throw an exception.
The person who throws an exception is the case when a currency that does not exist is specified in the calculation of money, and if you make a Money pattern [^ 3], I think that you will make an exception if it is an invalid currency in the constructor. I call these Value Objects sensitive Value Objects. [^ 3]: A famous pattern as a value object for money I think that the value objects that appear in Mr. Masanobu Naruse's "Introduction to Domain Driven Design" are basically sensitive Value Objects. [^ 4]
[^ 4]: I haven't read it properly, so I'm sorry if it's different ...
The case where you should return the result of failure is when the wrapper ValueObject specifies an unexpected value. I wrote the HttpStatusCodeValueObject
class in the code example of the wrapper ValueObject, but it works fine even if -1
is specified in status_code
. This is similar to the operation for NaN
in floating point calculation and NULL
in RDB. You can also use it for user input validation by creating a valid?
Method that checks if a valid value is specified.
I call these Value Objects insensitive.
It is sensitive because it reacts as soon as an invalid value comes in, and it is insensitive because it is not noticed until valid?
Is called.
A technique for finding what you need is to put everything on your desk and then remove what you don't need. It's an easy-to-understand way to finish without missing anything. You can also use this method when finding a ValueObject.
First, look at the user story and screen you are working on, and write down the data used in Notepad. From that, we will remove the values that are just input and output.
Even if the user name is required, there are cases where it is better to leave it to the framework validation process without creating a sensitive ValueObject. Custom Validators for complex cases like phone numbers (https://railsguides.jp/active_record_validations.html#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83% A0% E3% 83% 90% E3% 83% AA% E3% 83% 87% E3% 83% BC% E3% 82% B7% E3% 83% A7% E3% 83% B3% E3% 82% 92% You may want to use E5% AE% 9F% E8% A1% 8C% E3% 81% 99% E3% 82% 8B).
Ask a question about the remaining values.
[^ 5]: Not just one value, but multiple values may be used as a set
If yes in either case, create a ValueObject. On top of that, I think it's okay to create the properties as a Value Object if necessary.
Note: For statically typed languages, Hungarian notation ([Application Hungarian](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%B3%E3%82%AC%E3%83] % AA% E3% 82% A2% E3% 83% B3% E8% A8% 98% E6% B3% 95 #% E3% 82% A2% E3% 83% 97% E3% 83% AA% E3% 82% B1% E3% 83% BC% E3% 82% B7% E3% 83% A7% E3% 83% B3% E3% 83% 8F% E3% 83% B3% E3% 82% AC% E3% 83% AA% I think it's okay to create a ValueObject without a method in order to realize E3% 82% A2% E3% 83% B3)) with a type.
This entry is for Data Dictionary (https://linyclar.github.io/software_development/requirements_analysis_driven_desgin/#%E3%83) in Requirements Analysis Driven Design (https://linyclar.github.io/software_development/requirements_analysis_driven_desgin/) % 87% E3% 83% BC% E3% 82% BF% E3% 83% 87% E3% 82% A3% E3% 82% AF% E3% 82% B7% E3% 83% A7% E3% 83% 8A % E3% 83% AA) has been reorganized and rewritten to general-purpose content. It's a long story, but it's a story about DDD-like things in Rails.
Recommended Posts