SlideShare ist ein Scribd-Unternehmen logo
1 von 68
Downloaden Sie, um offline zu lesen
JakubPilimon
Refactor And Do It
Safely
Jakub Pilimon
JakubPilimon
SZCZEBRZESZYN
CHRZĄSZCZYRZEWOSZYCE
JakubPilimon
JakubPilimon
Jakub Pilimon
Principal Technologist @Pivotal
•Domain-Driven Design
•Test-Driven Development
•Architecture
•Spring
pillopl.github.io
#dddbyexamples
https://github.com/ddd-by-examples
JakubPilimon
JakubPilimon
WHAT THIS TALK IS
NOT ABOUT
JakubPilimon
JakubPilimon
JakubPilimon
BIG BALL OF MUD
JakubPilimon
NO STRUCTURE
SLOPPY
DUCT-TYPE STYLE
JakubPilimon
COUPLING
JakubPilimon
HARD TO
UNDERSTAND
MAINTAIN
DELIVER VALUE
JakubPilimon
TIME TO
REFACTOR?
JakubPilimon
IS THERE ANY
BUSINESS VALUE
LOST?
JakubPilimon
OUR JOB AS ENGINEERS
IS BRINGING VALUE,
NOT JUST SATISFYING
OUR AESTHETICS
NEEDS
JakubPilimon
WHAT IS
REFACTORING
ANYWAYS?
JakubPilimon
–Michael C. Feathers
“Code refactoring is the process of
restructuring existing computer code
—changing the factoring—
without changing its observable
behavior. (!!!)”
JakubPilimon
NOBODY IS GOING
TO PAY FOR THAT
JakubPilimon
DON’T SAY
“REFACTORING”
JakubPilimon
CREATE COMMON
UNDERSTANDING
“Introducing Event
Storming” by Alberto
Brandolini
JakubPilimon
USE MEASURABLE DATA
“Introducing Event Storming” by Alberto
Brandolini
JakubPilimon
USE MAGIC*
*MAGIC = ENGINEERING
JakubPilimonJakubPilimon
JakubPilimon
TREAT REFACTORING
AS A FEATURE
THAT ENABLES YOU TO
DELIVER FEATURES
QUICKER
JakubPilimonJakubPilimon
•Desirable observable
behavior of 🦄 is the
same as 💩 - it is
refactoring
• 🦄 is clean and ready
for new requirements
How it works
JakubPilimon
HOW TO REFACTOR?
JakubPilimon
HOW TO REFACTOR
WITHOUT TESTS?
“…without changing its observable behavior…”
JakubPilimon
OBSERVABLE BEHAVIORS
CAN BE EXPRESSED AS
AUTOMATED TEST SCENARIOS
*ALMOST ALWAYS
JakubPilimon
UNIT
INTEGRATION
E2E
JakubPilimon
JakubPilimon
JakubPilimon
JakubPilimon
IF IT WAS EASY TO ADD
UNIT TESTS IT WOULD
NOT BE A BIG BALL OF
MUD
JakubPilimon
UNIT
INTEGRATION
E2EE2E
INTEGRATION
UNIT
JakubPilimon
HOW TO FIND
SCENARIOS TO
TEST?
JakubPilimon
LOOK AT THE WALL
Place CollectOnly if
books on
Placed Book
Hold
Hold
Collected
Books
Placed on
Hold
JakubPilimonJakubPilimon
Place Book
On Hold
Only if
books on
hold < 5
HoldExpir
ed
4. Rule
2. Command
Placed On
Hold 1. Domain Event
Collected
Books View
View
3. Query
JakubPilimonJakubPilimon
Black-box testing of observable behaviors
Collect
Book
Collected
Books View
Command
API call
queue listener
db script
executed…
Query
API call
Excel sheet
DB query
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
JakubPilimonJakubPilimon
Black-box testing of observable behaviors
@Test
public void patronCanHoldABook() {
//given
BookEntity book = fixtures.aCirculatingBookAvailableForLending();
//and
BookHolderEntity patron = fixtures.aRegularPatron();
//when
patronWantsToHoldBook(patron, book);
//then
assertThat(placedOnHoldsBooksBy(patron)).containsExactlyInAnyOrder(book);
}
Hold
Book
Books on
Hold View
Observable Behaviors!!!
JakubPilimonJakubPilimon
Exploratory testing against present data sets
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Production copy/Staging
Amy John
JakubPilimonJakubPilimon
Reverse engineering from tests to business rules
@Test
public void patronCanHoldABook() {
//given
BookEntity book = bookFromDb(“Domain-Driven Design”);
//and
BookHolderEntity john = fromDb(“John”);
//and
BookHolderEntity amy = fromDb(“Amy”);
//when
assertThatExceptionIsThrown(()-> patronWantsToHoldBook(john, book)); //exception
patronWantsToHoldBook(amy, book); //success
//then
assertThat(placedOnHoldsBooksBy(amy)).containsExactlyInAnyOrder(book);
}
Heuristics about rules
5 holds
rule?
Researcher
can book
Restricted
books?
John has
overdue
books?
DDD
Book is
special?
Why
exception?
Why
success?
JakubPilimon
How to make the transition?
JakubPilimon
Option #1: Climbing step by step (💩 slowly becomes 🦄)
Option #2: Blue/Green refactoring (🦄 side by side with 💩)
JakubPilimonJakubPilimon
• 💩 slowly becomes 🦄
•Observable effects of 💩
kept
•Cannot easily rollback to
💩 from 🦄
•Need to dig in 💩
•Need to maintain one
model
Step by Step Refactoring
JakubPilimon
JakubPilimonJakubPilimon
• 💩 remains untouched
•Observable behavior of 💩
kept
•Easy to rollback to 💩 from
🦄
• 🦄 feels a bit like a green
field in 💩
•Need to maintain both 💩 🦄
•Need to somehow plugin 🦄
to the observable behaviors
Blue/Green Refactoring
JakubPilimon
JakubPilimon
Place
Book On
Hold
Collected
Books View
How it works
Collect
Book
Placed on
Hold Books
View
JakubPilimon
How to plug in new model to
observable behaviors?
JakubPilimon
–Michael C. Feathers
“A seam is a place where you can alter behaviour
in your program without editing in that place”
JakubPilimon
HOW TO LOOK FOR
SEEMS?
Collected
Books View
Place
Book On
Hold
JakubPilimon
public List<BookDto> booksPlacedOnHoldBy(UUID patronId, List<BookDto> oldModel) {
if (NewModelToggles.USE_NEW_MODEL.isActive()) {
List<BookDto> newModel = newLendingModel.booksOnHoldBy(patronId));
reconciliation.compare(oldModel, newModel);
return newModel;
}
return oldModel;
}
Anti-Corruption Layer
@GetMapping("/holds/{holderId}")
public ResponseEntity<List<BookDto>> getPlacedOnHoldBooks(UUID holderId) {
List<BookDto> oldModel = bookHolderService.getBooks(holderId);
return ResponseEntity.ok(lendingACL.booksPlacedOnHoldBy(holderId, oldModel));
}
Collected
Books View
JakubPilimon
Switch between models while querying
Collected
Books View
OR
Uses Feature Toggles for Querying
Query
JakubPilimon
Anti-Corruption Layer
@PostMapping("/books/collections")
public ResponseEntity collect(BookRequest bookRequest) {
oldModel.createCollectedBook(bookRequest.getBookId(),
bookRequest.getDays());
lendingACL.collect(bookRequest);
return ResponseEntity.ok().build();
}
Place
Book On
Hold
JakubPilimon
Parallel Operations For Commands
Collect
AND
Command
JakubPilimon
New model command failure cannot affect old
model result!
try/catch/log - might be your friend
JakubPilimon
JakubPilimon
Dependencies between packages
Old Lending Model New Lending Model
JakubPilimon
public List<BookDto> booksPlacedOnHoldBy(UUID patronId, List<BookDto> oldModel) {
if (NewModelToggles.RECONCILE_NEW_MODEL.isActive()) {
List<BookDto> newModel = newLendingModel.booksOnHoldBy(patronId));
reconciliation.compare(oldModel, newModel);
return oldModel;
}
if (NewModelToggles.RECONCILE_AND_USE_NEW_MODEL.isActive()) {
List<BookDto> newModel = newLendingModel.booksOnHoldBy(patronId));
reconciliation.compare(oldModel, newModel);
return newModel;
}
return oldModel;
}
Reconciliation in the ACL
JakubPilimon
Reconciliation
class Reconciliation<T> {
private final Reaction reaction;
public Diff<T> compare(Set<T> oldOne, Set<T> newOne) {
Diff<T> difference = new Diff<>(oldOne, newOne);
if (difference.exists()) {
reaction.reactTo(difference);
}
return difference;
}
}
interface Reaction {
void reactTo(Diff diff);
static Reaction logAndThanDisableToggle() {
return new CompositeReaction(justLog(), diff -> disableToggle());
}
static Reaction justLog() {
return System.out::println;
}
}
JakubPilimon
Add new model
to a next seam
Go to prod
Switch toggle to old model
Reconcile
Gather feedback
Switch toggle to new model
JakubPilimon
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Black-box testing of observable behaviors should
pass against two models!
JakubPilimon
Black-box testing of observable behaviors should
pass against two models!
@Test
public void patronCanHoldABook() {
//given
BookEntity book = fixtures.aCirculatingBookAvailableForLending();
//and
BookHolderEntity patron = fixtures.aRegularPatron();
//when
patronWantsToHoldBookInNewModel(patron, book);
//then
assertThat(newModelPlacedOnHoldsBooksBy(patron)).containsExactlyInAnyOrder(book);
}
@Rule
public TogglzRule togglzRule = TogglzRule.allDisabled(NewModelToggles.class);
List<UUID> newModelPlacedOnHoldsBooksBy(BookHolderEntity aRegularPatron) {
togglzRule.enable(NewModelToggles.RECONCILE_AND_USE_NEW_MODEL);
//..
}
JakubPilimon
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Automated verification of two models
@Test
public void researcherCanPlaceOpenEndedHolds() {
//given
BookHolderEntity aResearcherPatron = fixtures.aResearcherPatron();
//when
patronWantsToHoldBookForOpenEndedHold(aResearcherPatron, aCirculatingBook);
//and
List<UUID> oldModel = oldModelPlacedOnHoldsBooksBy(aResearcherPatron);
List<UUID> newModel = newModelPlacedOnHoldsBooksBy(aResearcherPatron);
//then
assertThat(oldModel).containsExactlyInAnyOrderElementsOf(newModel);
}
JakubPilimon
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Production copy/Staging
Amy John
JakubPilimon
Exploratory testing against present data set after data
migration from 💩 to 🦄
Place
Book On
Hold
Collected
Books View
Collect
Book
Placed on
Hold Books
View
Production copy/Staging
Amy John
JakubPilimon
UNIT
INTEGRATION
E2EE2E
INTEGRATION
UNIT
JakubPilimon
When we are sure…
Collected
Books View
3
JakubPilimon
Dependencies between packages
Old Lending Model New Lending Model
BBOM
JakubPilimon
JakubPilimon
Modular monolith
Is the system modular?
Isthesystemdistributed?
Big Ball Of Mud
Distributed
Big Ball Of Mud
Modular Monolith
Microservices
JakubPilimon
• Always, ALWAYS! Test if ACL works correctly (returning
correct models depending on the toggle settings)
• Always test if ACL does reconciliation correctly!
• Add test that checks that your feature toggle is disabled/
enabled by default
• Go to production and reconcile very quickly (after 1st
seam)
• Make sure new model failures do not affect old model
• With B/G Refactoring you are limited to programming
language and database of BBOM
JakubPilimon
Lessons Learned
JakubPilimon
https://github.com/pilloPl/bigballofmud
THANK YOU!

Weitere ähnliche Inhalte

Mehr von VMware Tanzu

Mehr von VMware Tanzu (20)

Spring Cloud Gateway - SpringOne Tour 2023 Charles Schwab.pdf
Spring Cloud Gateway - SpringOne Tour 2023 Charles Schwab.pdfSpring Cloud Gateway - SpringOne Tour 2023 Charles Schwab.pdf
Spring Cloud Gateway - SpringOne Tour 2023 Charles Schwab.pdf
 
Simplify and Scale Enterprise Apps in the Cloud | Boston 2023
Simplify and Scale Enterprise Apps in the Cloud | Boston 2023Simplify and Scale Enterprise Apps in the Cloud | Boston 2023
Simplify and Scale Enterprise Apps in the Cloud | Boston 2023
 
Simplify and Scale Enterprise Apps in the Cloud | Seattle 2023
Simplify and Scale Enterprise Apps in the Cloud | Seattle 2023Simplify and Scale Enterprise Apps in the Cloud | Seattle 2023
Simplify and Scale Enterprise Apps in the Cloud | Seattle 2023
 
tanzu_developer_connect.pptx
tanzu_developer_connect.pptxtanzu_developer_connect.pptx
tanzu_developer_connect.pptx
 
Tanzu Virtual Developer Connect Workshop - French
Tanzu Virtual Developer Connect Workshop - FrenchTanzu Virtual Developer Connect Workshop - French
Tanzu Virtual Developer Connect Workshop - French
 
Tanzu Developer Connect Workshop - English
Tanzu Developer Connect Workshop - EnglishTanzu Developer Connect Workshop - English
Tanzu Developer Connect Workshop - English
 
Virtual Developer Connect Workshop - English
Virtual Developer Connect Workshop - EnglishVirtual Developer Connect Workshop - English
Virtual Developer Connect Workshop - English
 
Tanzu Developer Connect - French
Tanzu Developer Connect - FrenchTanzu Developer Connect - French
Tanzu Developer Connect - French
 
Simplify and Scale Enterprise Apps in the Cloud | Dallas 2023
Simplify and Scale Enterprise Apps in the Cloud | Dallas 2023Simplify and Scale Enterprise Apps in the Cloud | Dallas 2023
Simplify and Scale Enterprise Apps in the Cloud | Dallas 2023
 
SpringOne Tour: Deliver 15-Factor Applications on Kubernetes with Spring Boot
SpringOne Tour: Deliver 15-Factor Applications on Kubernetes with Spring BootSpringOne Tour: Deliver 15-Factor Applications on Kubernetes with Spring Boot
SpringOne Tour: Deliver 15-Factor Applications on Kubernetes with Spring Boot
 
SpringOne Tour: The Influential Software Engineer
SpringOne Tour: The Influential Software EngineerSpringOne Tour: The Influential Software Engineer
SpringOne Tour: The Influential Software Engineer
 
SpringOne Tour: Domain-Driven Design: Theory vs Practice
SpringOne Tour: Domain-Driven Design: Theory vs PracticeSpringOne Tour: Domain-Driven Design: Theory vs Practice
SpringOne Tour: Domain-Driven Design: Theory vs Practice
 
SpringOne Tour: Spring Recipes: A Collection of Common-Sense Solutions
SpringOne Tour: Spring Recipes: A Collection of Common-Sense SolutionsSpringOne Tour: Spring Recipes: A Collection of Common-Sense Solutions
SpringOne Tour: Spring Recipes: A Collection of Common-Sense Solutions
 
SpringOne Tour: Doing Progressive Delivery with your Team
SpringOne Tour: Doing Progressive Delivery with your TeamSpringOne Tour: Doing Progressive Delivery with your Team
SpringOne Tour: Doing Progressive Delivery with your Team
 
SpringOne Tour: Make the Right Thing the Obvious Thing: The Journey to Intern...
SpringOne Tour: Make the Right Thing the Obvious Thing: The Journey to Intern...SpringOne Tour: Make the Right Thing the Obvious Thing: The Journey to Intern...
SpringOne Tour: Make the Right Thing the Obvious Thing: The Journey to Intern...
 
SpringOne Tour: An Introduction to Azure Spring Apps Enterprise
SpringOne Tour: An Introduction to Azure Spring Apps EnterpriseSpringOne Tour: An Introduction to Azure Spring Apps Enterprise
SpringOne Tour: An Introduction to Azure Spring Apps Enterprise
 
SpringOne Tour: 10 Practical Tips for Building Native and Serverless Spring A...
SpringOne Tour: 10 Practical Tips for Building Native and Serverless Spring A...SpringOne Tour: 10 Practical Tips for Building Native and Serverless Spring A...
SpringOne Tour: 10 Practical Tips for Building Native and Serverless Spring A...
 
SpringOne Tour: Spring Boot 3 and Beyond
SpringOne Tour: Spring Boot 3 and BeyondSpringOne Tour: Spring Boot 3 and Beyond
SpringOne Tour: Spring Boot 3 and Beyond
 
SpringOne Tour 2023: Let's Get Streaming! A Guide to Orchestrating Spring Clo...
SpringOne Tour 2023: Let's Get Streaming! A Guide to Orchestrating Spring Clo...SpringOne Tour 2023: Let's Get Streaming! A Guide to Orchestrating Spring Clo...
SpringOne Tour 2023: Let's Get Streaming! A Guide to Orchestrating Spring Clo...
 
Tanzu Developer Connect | Public Sector | March 29, 2023.pdf
Tanzu Developer Connect | Public Sector | March 29, 2023.pdfTanzu Developer Connect | Public Sector | March 29, 2023.pdf
Tanzu Developer Connect | Public Sector | March 29, 2023.pdf
 

Kürzlich hochgeladen

Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
masabamasaba
 

Kürzlich hochgeladen (20)

Artyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptxArtyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptx
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
WSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security Program
 

Refactor And Do It Safely - Jakub Pilimon