Skip to content

Instantly share code, notes, and snippets.

@delbetu
Last active October 11, 2022 08:02
Show Gist options
  • Save delbetu/55b21056f9fcf7b33f0187484cd28061 to your computer and use it in GitHub Desktop.
Save delbetu/55b21056f9fcf7b33f0187484cd28061 to your computer and use it in GitHub Desktop.
Notes from - Gary Bernhardt boundaries talk.
# Procedural (Mutation and data separated from code)
# OO (Mutation and data combined with code)
# Functional Version (Imutability and data separated from code)
# FauxO Version (Immutability and data combined with code)
#Procedural-we have knowledge of the internals
def feeding_time
walruses.each do |walrus|
walrus.stomach << Cheese.new
end
end
#OO-we do not have knowledge of how eat is implemented
def feeding_time
walruses.each do |walrus|
walrus.eat(Cheese.new)
end
end
class Walrus
def eat(food)
@stomach << food
end
end
#Functional
#The idea is take old walruses and produce newones with more food inside
#Types -> hash for walrus, array for stomach and string for food
def feeding_time
walruses.map do |walrus|
eat(walrus, "cheese")
end
end
def eat(walrus, food)
stomach = warus.fetch(:stomach) + [food]
walrus.merge(stomach: stomach)
end
#FauxO
def feeding_time
walruses.map do |walrus|
walrus.eat(Cheese.new)
end
end
class Walrus
def eat(food)
stomach = @stomach + [food]
Walrus.new(@name, stomach)
end
end
#OO version (Mutation and data combined with code)
#Here we have three classes User, Sweeper and UserMailer
#Sweeper reads from User and Writes on UserMailer
class Sweeper
def self.sweep
User.all.select do |user|
end.each do |user|
UserMailer.billing_problem(user)
end
end
end
#The Isolation test stubs User and Mocks UserMailer
describe Sweeper do
context "when a subscription is expired" do
let(:bob) do
stub(active?: true, paid_at: 2.months.ago)
end
let(:users) { [bob] }
before { User.stub(:all) { users }}
it "emails the user" do
UserMailer.should_receive(:billing_problem).with(bob)
Sweeper.sweep
end
end
end
# Starting with OO version we will try to make the code have two properties
# 1- Every function has Value IN - Value Out
# 2- These function do not have dependencies
# Doing that we will notice that we are naturally isolating
# OO implementation
class Sweeper
def self.sweep
User.all.select do |user|
end.each do |user|
UserMailer.billing_problem(user)
end
end
end
#FauxO implementation
#The nature of the comunication between the components has changed
#Instead of having synchronous methods calls as boundary between things
#We have Value as boundary
class ExpiredUsers
def self.for_users(users)
users.select do |user|
user.active? && user.paid_at < 1.month.ago
end
end
end
#ExpiredUsers is the functional core
#Sweeper is the imperative shell which combines destructive operations with the functional core
class Sweeper
def self.sweep
ExpiredUsers.for_users(User.all).each do |user|
UserMailer.billing_problem(user)
end
end
end
#ExpiredUsers has all the decisions while Sweeper has all the dependencies
describe Sweeper do
context "when a subscription is expired" do
let(:bob) do
#stub(active?: true, paid_at: 2.months.ago) replace with a data stucture
User.new(active?: true, paid_at: 2.months.ago)
end
let(:users) { [bob] }
#before { User.stub(:all) { users }} Delete this
it "emails the user" do
#UserMailer.should_receive(:billing_problem).with(bob)
#Sweeper.sweep
ExpiredUsers.for_users(users).should == [bob]
end
end
end
# Having the code in FauxO style let us make it concurrent without worries for
# race conditions since we do not have mutability
# We could run ExpiredUsers, UserMailer and User db in separated threads.
# They live in a loop waiting for data to be processed
# The Sweeper class just make the data flow between them by pushing and poping
# into and from their queues
#Example echo program (actor model)
queue = Queue.new #Inbox for Process 2
Thread.new { loop { queue.push(gets) } } #Process 1
Thread.new { loop { puts(queue.pop) } }.join #Process 2
# Actor model for previous problem
actor Sweeper
User.all.each { |user| ExpiredUsers.push(user) }
die
end
actor ExpiredUsers
user = pop
late = user.active? && user.paid_at < 1.month.ago
BillingProblemNotification.push(user)
end
actor BillingProblemNotification
UserMailer.billing_problem(pop)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment