SlideShare a Scribd company logo
1 of 71
Download to read offline
CENTRALIZE YOUR
BUSINESS LOGIC
WITH PIPELINES1
OUTLINE
> PagerDuty & Webhooks
> Old Code - Scattered
> New Code - Centralized
> Results :)
> Recap
2
WHAT YOU SHOULD TAKE AWAY
Centralized business logic leads to:
> More readable code
> Clearer business logic
> Happier team!
3
4
PAGERDUTY &
WEBHOOKS
AN ANALOGY
5
6
7
8
9
10
11
{
"payload": {
"type": "incident.trigger",
"data": {
"title": "Response times are spiking!",
}
},
"endpoint": "https://slack.api.com/message/create"
}
12
13
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
)
14
15
16
Our goal when using a functional
paradigm is to think out our
program as one big function,
transforming its inputs into
outputs
— Dave Thomas, "Elixir for Programmers"
17
THE STORY SO FAR
> PagerDuty enables engineers to go on-call
> Webhooks let you send data to different systems
> My teams owns the webhooks service
18
OLD CODE
SCATTERED
19
20
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
21
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
21
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
21
defmodule Parser do
@expired_threshold_seconds 5
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.endpoint
payload = decoded_webhook.payload
time_since_created = decoded_webhook.created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true -> {:error, "webhook expired"}
false -> {:ok, endpoint, payload}
end
end
22
defmodule Parser do
@expired_threshold_seconds 5
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.endpoint
payload = decoded_webhook.payload
time_since_created = decoded_webhook.created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true -> {:error, "webhook expired"}
false -> {:ok, endpoint, payload}
end
end
22
defmodule Parser do
@expired_threshold_seconds 5
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.endpoint
payload = decoded_webhook.payload
time_since_created = decoded_webhook.created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true -> {:error, "webhook expired"}
false -> {:ok, endpoint, payload}
end
end
22
defmodule Parser do
@expired_threshold_seconds 5
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.endpoint
payload = decoded_webhook.payload
time_since_created = decoded_webhook.created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true -> {:error, "webhook expired"}
false -> {:ok, endpoint, payload}
end
end
22
defmodule Parser do
@expired_threshold_seconds 5
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.endpoint
payload = decoded_webhook.payload
time_since_created = decoded_webhook.created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true -> {:error, "webhook expired"}
false -> {:ok, endpoint, payload}
end
end
22
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
23
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
23
defmodule Processor do
def process(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload} ->
HTTP.post(endpoint, payload)
{:error, error_msg} ->
Logger.error("error - #{error_msg}")
end
end
end
23
Bad Modularization
24
Scattered Business Logic
25
NEW CODE
CENTRALIZED
26
27
28
defmodule Processor do
defmodule WebhookState do
defstruct(
endpoint: "",
payload: "",
created_at: ""
)
end
29
defmodule Processor do
defmodule WebhookState do
defstruct(
endpoint: "",
payload: "",
created_at: ""
)
end
29
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
30
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
30
BUT THAT'S NOT
A |> PIPELINE!?
31
parse(raw_webhook)
|> check_expired(webhook)
|> send(webhook)
32
defp parse(:error), do: :error
defp parse(webhook)
...
end
defp check_expired(:error), do: :error
defp check_expired(webhook)
...
end
defp send(:error), do: :error
defp send(webhook)
...
end
33
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
:ok
else
:error
end
34
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
:ok
else
:error
end
34
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
:ok
else
:error
end
34
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
35
defp parse(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload, created_at} ->
{:ok, %WebhookState{
endpoint: endpoint,
payload: payload,
created_at: created_at
}}
{:error, error_msg} ->
{:error, "failed to parse webhook"}
end
end
36
defp parse(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload, created_at} ->
{:ok, %WebhookState{
endpoint: endpoint,
payload: payload,
created_at: created_at
}}
{:error, error_msg} ->
{:error, "failed to parse webhook"}
end
end
36
defmodule Parser do
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.event.endpoint
payload = decoded_webhook.event.payload
created_at = decoded_webhook.event.created_at
# no more expired webhook logic :) !
{:ok, endpoint, payload, created_at}
end
end
37
defmodule Parser do
def parse(raw_webhook) do
decoded_webhook = JSON.decode(raw_webhook)
endpoint = decoded_webhook.event.endpoint
payload = decoded_webhook.event.payload
created_at = decoded_webhook.event.created_at
# no more expired webhook logic :) !
{:ok, endpoint, payload, created_at}
end
end
37
defp parse(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload, created_at} ->
{:ok, %WebhookState{
endpoint: endpoint,
payload: payload,
created_at: created_at
}}
{:error, error_msg} ->
{:error, "failed to parse webhook"}
end
end
38
defp parse(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload, created_at} ->
{:ok, %WebhookState{
endpoint: endpoint,
payload: payload,
created_at: created_at
}}
{:error, error_msg} ->
{:error, "failed to parse webhook"}
end
end
38
defp parse(raw_webhook) do
parsed_webhook = Parser.parse(raw_webhook)
case parsed_webhook do
{:ok, endpoint, payload, created_at} ->
{:ok, %WebhookState{
endpoint: endpoint,
payload: payload,
created_at: created_at
}}
{:error, error_msg} ->
{:error, "failed to parse webhook"}
end
end
38
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
39
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
39
@expired_threshold_seconds 5
defp check_expired(webhook = %WebhookState{created_at: created_at}) do
# this was moved from Parser to Processor :) !
time_since_created = created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true ->
{:error, "webhook expired"}
false ->
{:ok, webhook}
end
end
40
@expired_threshold_seconds 5
defp check_expired(webhook = %WebhookState{created_at: created_at}) do
# this was moved from Parser to Processor :) !
time_since_created = created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true ->
{:error, "webhook expired"}
false ->
{:ok, webhook}
end
end
40
@expired_threshold_seconds 5
defp check_expired(webhook = %WebhookState{created_at: created_at}) do
# this was moved from Parser to Processor :) !
time_since_created = created_at - Time.now
is_expired = time_since_created > @expired_threshold_seconds * 1000
case is_expired do
true ->
{:error, "webhook expired"}
false ->
{:ok, webhook}
end
end
40
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
41
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
) do
Logger.info("webhook processed successfully :) !")
:ok
else
{:error, msg} ->
Logger.error("error processing webhook :( ! - #{msg}")
:error
end
end
41
defp send(webhook = %WebhookState{endpoint: endpoint, payload: payload}) do
HTTP.post(endpoint, payload)
end
42
defp send(webhook = %WebhookState{endpoint: endpoint, payload: payload}) do
HTTP.post(endpoint, payload)
end
42
43
Good modularization!
44
Centralized Business Logic!
45
RESULTS :)
46
47
RECAP
48
def process(raw_webhook) do
with(
{:ok, webhook} <- parse(raw_webhook),
{:ok, webhook} <- check_expired(webhook),
{:ok, webhook} <- send(webhook)
)
> Good modularization! ✓
> Centralized business logic! ✓
> Happy team! ✓
49
LINKS
> Slides - bit.ly/2HINSBU
> Blog post - bit.ly/2Nb5shE
> Elixir course - bit.ly/2SdseX2
> LinkedIn - linkedin.com/in/MichaelViveros/
> MichaelViveros@gmail.com
> QUESTIONS?
50

More Related Content

What's hot

HTTP For the Good or the Bad - FSEC Edition
HTTP For the Good or the Bad - FSEC EditionHTTP For the Good or the Bad - FSEC Edition
HTTP For the Good or the Bad - FSEC EditionXavier Mertens
 
Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeWim Godden
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and PythonPiXeL16
 
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...Codemotion
 
VCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyVCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyFastly
 
Advanced VCL: how to use restart
Advanced VCL: how to use restartAdvanced VCL: how to use restart
Advanced VCL: how to use restartFastly
 
Static Typing in Vault
Static Typing in VaultStatic Typing in Vault
Static Typing in VaultGlynnForrest
 
When dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWhen dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWim Godden
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalabilityWim Godden
 
Eve - REST API for Humans™
Eve - REST API for Humans™Eve - REST API for Humans™
Eve - REST API for Humans™Nicola Iarocci
 
Bootstrapping multidc observability stack
Bootstrapping multidc observability stackBootstrapping multidc observability stack
Bootstrapping multidc observability stackBram Vogelaar
 
Python: the coolest is yet to come
Python: the coolest is yet to comePython: the coolest is yet to come
Python: the coolest is yet to comePablo Enfedaque
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalabilityWim Godden
 
Get Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsGet Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsdaviddemello
 
Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slidesharetomcopeland
 
Server Side Events
Server Side EventsServer Side Events
Server Side Eventsthepilif
 
Remove php calls and scale your site like crazy !
Remove php calls and scale your site like crazy !Remove php calls and scale your site like crazy !
Remove php calls and scale your site like crazy !Wim Godden
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineAndy McKay
 

What's hot (20)

HTTP For the Good or the Bad - FSEC Edition
HTTP For the Good or the Bad - FSEC EditionHTTP For the Good or the Bad - FSEC Edition
HTTP For the Good or the Bad - FSEC Edition
 
Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the code
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
 
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
 
VCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyVCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to Fastly
 
Advanced VCL: how to use restart
Advanced VCL: how to use restartAdvanced VCL: how to use restart
Advanced VCL: how to use restart
 
Static Typing in Vault
Static Typing in VaultStatic Typing in Vault
Static Typing in Vault
 
When dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWhen dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniques
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalability
 
Eve - REST API for Humans™
Eve - REST API for Humans™Eve - REST API for Humans™
Eve - REST API for Humans™
 
Bootstrapping multidc observability stack
Bootstrapping multidc observability stackBootstrapping multidc observability stack
Bootstrapping multidc observability stack
 
Python: the coolest is yet to come
Python: the coolest is yet to comePython: the coolest is yet to come
Python: the coolest is yet to come
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalability
 
Get Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsGet Real: Adventures in realtime web apps
Get Real: Adventures in realtime web apps
 
Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshare
 
Server Side Events
Server Side EventsServer Side Events
Server Side Events
 
Remove php calls and scale your site like crazy !
Remove php calls and scale your site like crazy !Remove php calls and scale your site like crazy !
Remove php calls and scale your site like crazy !
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App Engine
 
MySQL in your laptop
MySQL in your laptopMySQL in your laptop
MySQL in your laptop
 
Introduction to Flask Micro Framework
Introduction to Flask Micro FrameworkIntroduction to Flask Micro Framework
Introduction to Flask Micro Framework
 

Similar to Centralize your Business Logic with Pipelines in Elixir

Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Masahiro Nagano
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCLFastly
 
The Promised Land (in Angular)
The Promised Land (in Angular)The Promised Land (in Angular)
The Promised Land (in Angular)Domenic Denicola
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesLindsay Holmwood
 
Taking Web Apps Offline
Taking Web Apps OfflineTaking Web Apps Offline
Taking Web Apps OfflinePedro Morais
 
Complex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxComplex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxbobmcwhirter
 
Spring data iii
Spring data iiiSpring data iii
Spring data iii명철 강
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless BallerinaBallerina
 
Building Scalable Websites with Perl
Building Scalable Websites with PerlBuilding Scalable Websites with Perl
Building Scalable Websites with PerlPerrin Harkins
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiInfluxData
 
HTTP Caching and PHP
HTTP Caching and PHPHTTP Caching and PHP
HTTP Caching and PHPDavid de Boer
 
Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Ismael Celis
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applicationsTom Croucher
 
OpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonOpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonPyCon Italia
 
Webinar: Index Tuning and Evaluation
Webinar: Index Tuning and EvaluationWebinar: Index Tuning and Evaluation
Webinar: Index Tuning and EvaluationMongoDB
 
Demo how to create visualforce and apex controller to update, delete custom o...
Demo how to create visualforce and apex controller to update, delete custom o...Demo how to create visualforce and apex controller to update, delete custom o...
Demo how to create visualforce and apex controller to update, delete custom o...tuan vo
 

Similar to Centralize your Business Logic with Pipelines in Elixir (20)

Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015
 
Puppet Camp 2012
Puppet Camp 2012Puppet Camp 2012
Puppet Camp 2012
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCL
 
The Promised Land (in Angular)
The Promised Land (in Angular)The Promised Land (in Angular)
The Promised Land (in Angular)
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websites
 
Taking Web Apps Offline
Taking Web Apps OfflineTaking Web Apps Offline
Taking Web Apps Offline
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
Complex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxComplex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBox
 
Spring data iii
Spring data iiiSpring data iii
Spring data iii
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless Ballerina
 
Building Scalable Websites with Perl
Building Scalable Websites with PerlBuilding Scalable Websites with Perl
Building Scalable Websites with Perl
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
 
HTTP Caching and PHP
HTTP Caching and PHPHTTP Caching and PHP
HTTP Caching and PHP
 
Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
JavaScript Lessons 2023
JavaScript Lessons 2023JavaScript Lessons 2023
JavaScript Lessons 2023
 
OpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonOpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con Python
 
Webinar: Index Tuning and Evaluation
Webinar: Index Tuning and EvaluationWebinar: Index Tuning and Evaluation
Webinar: Index Tuning and Evaluation
 
Demo how to create visualforce and apex controller to update, delete custom o...
Demo how to create visualforce and apex controller to update, delete custom o...Demo how to create visualforce and apex controller to update, delete custom o...
Demo how to create visualforce and apex controller to update, delete custom o...
 

Recently uploaded

DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 

Recently uploaded (20)

DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 

Centralize your Business Logic with Pipelines in Elixir

  • 2. OUTLINE > PagerDuty & Webhooks > Old Code - Scattered > New Code - Centralized > Results :) > Recap 2
  • 3. WHAT YOU SHOULD TAKE AWAY Centralized business logic leads to: > More readable code > Clearer business logic > Happier team! 3
  • 4. 4
  • 6. 6
  • 7. 7
  • 8. 8
  • 9. 9
  • 10. 10
  • 11. 11
  • 12. { "payload": { "type": "incident.trigger", "data": { "title": "Response times are spiking!", } }, "endpoint": "https://slack.api.com/message/create" } 12
  • 13. 13
  • 14. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) 14
  • 15. 15
  • 16. 16
  • 17. Our goal when using a functional paradigm is to think out our program as one big function, transforming its inputs into outputs — Dave Thomas, "Elixir for Programmers" 17
  • 18. THE STORY SO FAR > PagerDuty enables engineers to go on-call > Webhooks let you send data to different systems > My teams owns the webhooks service 18
  • 20. 20
  • 21. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 21
  • 22. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 21
  • 23. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 21
  • 24. defmodule Parser do @expired_threshold_seconds 5 def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.endpoint payload = decoded_webhook.payload time_since_created = decoded_webhook.created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, endpoint, payload} end end 22
  • 25. defmodule Parser do @expired_threshold_seconds 5 def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.endpoint payload = decoded_webhook.payload time_since_created = decoded_webhook.created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, endpoint, payload} end end 22
  • 26. defmodule Parser do @expired_threshold_seconds 5 def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.endpoint payload = decoded_webhook.payload time_since_created = decoded_webhook.created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, endpoint, payload} end end 22
  • 27. defmodule Parser do @expired_threshold_seconds 5 def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.endpoint payload = decoded_webhook.payload time_since_created = decoded_webhook.created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, endpoint, payload} end end 22
  • 28. defmodule Parser do @expired_threshold_seconds 5 def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.endpoint payload = decoded_webhook.payload time_since_created = decoded_webhook.created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, endpoint, payload} end end 22
  • 29. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 23
  • 30. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 23
  • 31. defmodule Processor do def process(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload} -> HTTP.post(endpoint, payload) {:error, error_msg} -> Logger.error("error - #{error_msg}") end end end 23
  • 35. 27
  • 36. 28
  • 37. defmodule Processor do defmodule WebhookState do defstruct( endpoint: "", payload: "", created_at: "" ) end 29
  • 38. defmodule Processor do defmodule WebhookState do defstruct( endpoint: "", payload: "", created_at: "" ) end 29
  • 39. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 30
  • 40. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 30
  • 41. BUT THAT'S NOT A |> PIPELINE!? 31
  • 43. defp parse(:error), do: :error defp parse(webhook) ... end defp check_expired(:error), do: :error defp check_expired(webhook) ... end defp send(:error), do: :error defp send(webhook) ... end 33
  • 44. with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do :ok else :error end 34
  • 45. with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do :ok else :error end 34
  • 46. with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do :ok else :error end 34
  • 47. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 35
  • 48. defp parse(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload, created_at} -> {:ok, %WebhookState{ endpoint: endpoint, payload: payload, created_at: created_at }} {:error, error_msg} -> {:error, "failed to parse webhook"} end end 36
  • 49. defp parse(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload, created_at} -> {:ok, %WebhookState{ endpoint: endpoint, payload: payload, created_at: created_at }} {:error, error_msg} -> {:error, "failed to parse webhook"} end end 36
  • 50. defmodule Parser do def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.event.endpoint payload = decoded_webhook.event.payload created_at = decoded_webhook.event.created_at # no more expired webhook logic :) ! {:ok, endpoint, payload, created_at} end end 37
  • 51. defmodule Parser do def parse(raw_webhook) do decoded_webhook = JSON.decode(raw_webhook) endpoint = decoded_webhook.event.endpoint payload = decoded_webhook.event.payload created_at = decoded_webhook.event.created_at # no more expired webhook logic :) ! {:ok, endpoint, payload, created_at} end end 37
  • 52. defp parse(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload, created_at} -> {:ok, %WebhookState{ endpoint: endpoint, payload: payload, created_at: created_at }} {:error, error_msg} -> {:error, "failed to parse webhook"} end end 38
  • 53. defp parse(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload, created_at} -> {:ok, %WebhookState{ endpoint: endpoint, payload: payload, created_at: created_at }} {:error, error_msg} -> {:error, "failed to parse webhook"} end end 38
  • 54. defp parse(raw_webhook) do parsed_webhook = Parser.parse(raw_webhook) case parsed_webhook do {:ok, endpoint, payload, created_at} -> {:ok, %WebhookState{ endpoint: endpoint, payload: payload, created_at: created_at }} {:error, error_msg} -> {:error, "failed to parse webhook"} end end 38
  • 55. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 39
  • 56. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 39
  • 57. @expired_threshold_seconds 5 defp check_expired(webhook = %WebhookState{created_at: created_at}) do # this was moved from Parser to Processor :) ! time_since_created = created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, webhook} end end 40
  • 58. @expired_threshold_seconds 5 defp check_expired(webhook = %WebhookState{created_at: created_at}) do # this was moved from Parser to Processor :) ! time_since_created = created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, webhook} end end 40
  • 59. @expired_threshold_seconds 5 defp check_expired(webhook = %WebhookState{created_at: created_at}) do # this was moved from Parser to Processor :) ! time_since_created = created_at - Time.now is_expired = time_since_created > @expired_threshold_seconds * 1000 case is_expired do true -> {:error, "webhook expired"} false -> {:ok, webhook} end end 40
  • 60. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 41
  • 61. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) do Logger.info("webhook processed successfully :) !") :ok else {:error, msg} -> Logger.error("error processing webhook :( ! - #{msg}") :error end end 41
  • 62. defp send(webhook = %WebhookState{endpoint: endpoint, payload: payload}) do HTTP.post(endpoint, payload) end 42
  • 63. defp send(webhook = %WebhookState{endpoint: endpoint, payload: payload}) do HTTP.post(endpoint, payload) end 42
  • 64. 43
  • 68. 47
  • 70. def process(raw_webhook) do with( {:ok, webhook} <- parse(raw_webhook), {:ok, webhook} <- check_expired(webhook), {:ok, webhook} <- send(webhook) ) > Good modularization! ✓ > Centralized business logic! ✓ > Happy team! ✓ 49
  • 71. LINKS > Slides - bit.ly/2HINSBU > Blog post - bit.ly/2Nb5shE > Elixir course - bit.ly/2SdseX2 > LinkedIn - linkedin.com/in/MichaelViveros/ > MichaelViveros@gmail.com > QUESTIONS? 50