[RUBY] Find a value that is convenient to have a method and make it a ValueObject

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.

Value Object pattern

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.

Wrapper ValueObject

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?.

Computable Value Object

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.

Sensitive ValueObjects and Insensitive ValueObjects

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.

Think "Isn't there a convenient value if there is a method?"

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

Find a value that is convenient to have a method and make it a ValueObject
How to create a convenient method that utilizes generics and functional interfaces
If you want to make a Java application a Docker image, it is convenient to use jib.
How to identify the path that is easy to make a mistake
A story that made it convenient with Kotlin that it is troublesome to execute animation continuously on Android
How to test a private method in Java and partially mock that method
A shell script that builds a Docker image and pushes it to ECR
JVM Performance Tuning: What is Tuning and How to Make a Good Plan
Create a program to post to Slack with GO and make it a container
A simple and convenient method for HashMap
How to convert a value of a different type and assign it to another variable
Code that is difficult to debug and parse
A function that displays only once while Alert is running and never displays it again
How to make a Vagrant Plugin that you learned when you forked and published vagrant-mutagen
Get the value from the array and find out what number it is included in
Pass arguments to the method and receive the result of the operation as a return value
A solution that makes it easy to input Procon and Web tests to verify results
I tried to make FizzBuzz that is uselessly flexible
Program to determine if it is a leap year
[Docker] Is it good enough to call it a multi-stage build? → The story that became so good
Session was a cookie designed to be erased when the browser was closed and was a method for exchanging it: Rails Tutorial Note-What is a Rails Session?
To you who lament that Java's main method is static
Assign a Java8 lambda expression to a variable and reuse it
Determine that the value is a multiple of 〇 in Ruby
Make Docker confusing with Pokemon and make it easier to attach
I want to make a list with kotlin and java!
I want to call a method and count the number
I want to make a function with kotlin and java!
Find a Switch statement that can be converted to a Switch expression
I'm glad to have a method to blink characters on Android
How to make th: value of select have multiple values
Create a Spring Boot web app that uses IBM Cloudant and deploy it to Cloud Foundry