Last active
October 11, 2022 08:02
-
-
Save delbetu/55b21056f9fcf7b33f0187484cd28061 to your computer and use it in GitHub Desktop.
Notes from - Gary Bernhardt boundaries talk.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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