It is a story that cuts out access to a specific data resource from business logic and processing processing in the service that is being operated and standardizes it.
I do not understand. Is Entity or domain object better? Please let me know.
For example, this is the code. The following is a class that manages the page of a prefecture, and it is assumed that you have acquired an event associated with that prefecture.
area/pref.rb
module Area
class Pref
def event
events = Api.call('/events', params)
events.each_with_object([]) do |event, memo|
memo << {
name: event[:name],
address: create_full_address(event)
}
end
end
def create_full_address(event)
#Returns the full string of the address, combining some information
end
end
end
Next, the page of cities, wards, towns and villages under the prefecture was created. Below you will find events related to that city.
area/city.rb
module Area
class City
def event
events = Api.call('/events', params)
events.each_with_object([]) do |event, memo|
memo << {
name: event[:name],
address: create_city_address(event)
}
end
end
def create_city_address(event)
#I want to display the address short, so I will return the address string after the city
end
end
end
The processing that handles event information is duplicated. Let's cut this out as follows and make it common.
area/common.rb
module Area
class Common
class << self
def events(params)
Api.call('/events', params)
end
def create_full_address(event)
#Returns the full string of the address, combining some information
end
def create_city_address(event)
#I want to display the address short, so I will return the address string after the city
end
end
end
end
The caller looks like the following.
area/pref.rb
require_relative 'common'
module Area
class Pref
def event
events = Common.events(params)
events.each_with_object([]) do |event, memo|
memo << {
name: event[:name],
address: Common.create_city_address(event)
}
end
end
end
end
I thought it was okay, but next there was a new class to manage the station pages. Suppose you don't need address information on this page, you only need information about the station.
Last time, I made common on area pages like area / common.rb. Also, since the information acquired on the area page is unnecessary and the information I want is slightly different, I decided to implement access to the event information on my own.
traffic/station.rb
module Traffic
class Station
def event(params)
events = Api.call('/events', params)
events.each_with_object([]) do |event, memo|
memo << {
name: event[:name],
stations: create_stations(event)
}
end
end
def create_stations(event)
#Returns some station information
end
end
end
The above is the general situation before modeling. The example is long and I feel that it is not good enough. Let me make an excuse that the situation is actually a little more complicated.
To summarize the problems, it can be said that the situation was as follows.
If the convenient API in the company is a data resource, a response that is somewhat attentive will be returned, so there may be cases where it is called in the business logic without being properly separated. I think. Even if it can be implemented simply at first, it will become chaotic when various uses start in various places.
As a solution, I decided to operate the model properly. I decided to cut the model for each data resource and always access the data resource via the model. By consolidating the decoration of the response into the model, we tried to standardize it and created a clear state where to implement it.
Specifically, it is as follows.
Define the model that manages each event as follows.
model/event.rb
module Model
class Event
attr_accessor :name, :address
def initialtize(event)
{
'name' => '@name',
'address' => '@address',
}.each do |key, prop|
instance_variable_set(prop, event[key])
end
end
def full_address
#Returns the full string of the address, combining some information
end
def city_address
#I want to display the address short, so I will return the address string after the city
end
def stations
#Returns some station information
end
end
end
The model that manages the list of events is as follows.
model/events.rb
require_relative 'event'
module Model
module Events
attr_accessor :events
def initialize(events)
@events = events.each_with_object([]) do |event, memo|
memo << Model::Event.new(event)
end
end
class << self
def get(params)
events = Api.call('/events', params)
Model::Events.new(events)
end
end
end
end
The user side looks like this.
area/pref.rb
module Area
class Pref
def event
events = Model::Events.get(params)
events.each_with_object([]) do |event, memo|
memo << {
name: event.name,
address: event.full_address
}
end
end
end
end
We have defined model / event.rb as a model that handles event information. This is in charge of holding and decorating event information, and all access to event information goes through this model.
Since the API response to get the event is returned as a list type, define model / events.rb and manage the API response here. At this time, individual event information is passed to event.rb for decoration, and events.rb keeps the result as a list.
As a result, users will be able to access the decorated items without being aware of it.
By modeling as described above, we were able to consolidate access and decoration to data resources.
On the other hand, with this design, there is a concern that the model will swell. As an effort to avoid the FAT model, we are taking the following measures.
Although it is a delegate, it is effective when there are other models for festival other than event, and you want to have a similar method for each.
model/event.rb
module Model
class Event
#Hold to judge the event holding?The method is Model::Transfer to Condition and use that implementation
delegate :hold?, to: Model::Condition
end
end
Can be called as follows
area/pref.rb
module Area
class Pref
def hold_event?
Model::Events.hold?
end
end
end
SpecialThanks Thank you to @nazomikan for providing the draft, HK and SS for promoting it, and all the members of the department!
Recommended Posts