Skip to content

Instantly share code, notes, and snippets.

@elct9620
Last active January 12, 2024 05:35
Show Gist options
  • Save elct9620/84f1c94726064b4be942f3f5b6cbf06a to your computer and use it in GitHub Desktop.
Save elct9620/84f1c94726064b4be942f3f5b6cbf06a to your computer and use it in GitHub Desktop.
Example of Domain-Driven Design in Rails

Example of Domain-Driven Design in Rails

Object

Name Layer Type Description
app/models/visitor_pass.rb Domain Aggregate Pass-related domain
app/services/tracker_service.rb Domain Domain Service Logic between door and visitor interaction
app/controllers/passes_controller.rb Application Use Case The user flow of "pass" a door

Test

Name Type Description
features/door_track.feature E2E Verify the user flow work correctly
spec/services/tracker_service_spec.rb Unit Verify the business logic work correctly
Feature: Track Door Visitors
Background:
Given there have some sensors
| identifier |
| cb5a68b8bb2e29a1 |
And there have some doors
| name |
| Main Arena Right |
And sensor "cb5a68b8bb2e29a1" is put in door "Main Arena Right"
And there have some visitor
| name |
| Aotoki |
Scenario: Visitor "Aotoki" can pass the door "Main Arena Right"
When "Aotoki" pass the "Main Arena Right" through sensor "cb5a68b8bb2e29a1"
Then I can receive pass in response
class PassesController < ActionController::API
include ConferenceTracker::Deps['tracker_service']
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from TrackerService::AccessDenied, with: :pass_access_denied
def create
visitor = Visitor.by_rfid(rfid: pass_params[:rfid])
door = Door.by_sensor(identifier: pass_params[:identifier])
pass = tracker_service.track(door, visitor: visitor)
pass.save
render json: {
pass: {
in: pass.in?
}
}
end
private
def pass_params
params.require(:pass).permit(:rfid, :identifier)
end
end
class TrackerService
def track(door, visitor:)
raise TrackerService::AccessDenied unless door.allow?(visitor)
pass = VisitorPass.new(visitor: visitor)
pass.build_event(door_id: door.id)
pass
end
end
RSpec.describe TrackerService do
subject(:service) { described_class.new }
describe '#track' do
subject(:track) { service.track(door, visitor: visitor) }
given_a_visitor(name: 'Aotoki')
given_a_door_allow_visitors(visitors: [visitor])
it { is_expected.to be_pass_out }
it { is_expected.to be_a(VisitorPass)
context 'when visitor not allowed' do
given_a_door_not_allow_anyone
it { expect { track }.to raise_error(TrackerService::AccessDenied) }
end
end
end
class VisitorPass
def initialize(visitor:)
@visitor = visitor
@pending_events = []
end
def events
PassEvent.where(visitor: @visitor)
end
def build_event(**attributes)
@pending_events << events.build(**attributes)
end
def in?
events.size.odd?
end
def save
PassEvent.import @pending_events.slice!(0..-1)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment