Apache Kafka is a battle-tested stream-processing platform. Combined with Kafka Streams API, it’s possible to build scalable, fault-tolerant and distributed event-driven systems. In this talk, I will describe the key components of an event sourced system and how simple building blocks can be combined using the powerful and declarative streams API to deliver business value. I will demonstrate possible solutions to the challenges of eventual-consistency and the object-relational impedance mismatch.
22. HOW DOYOU HYDRATE
YOUR DOMAIN OBJECTS?
SELECT
e.employee_id AS "Employee #"
, e.first_name || ' ' || e.last_name AS "Name"
, e.email AS "Email"
, e.phone_number AS "Phone"
, TO_CHAR(e.hire_date, 'MM/DD/YYYY') AS "Hire Date"
, TO_CHAR(e.salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') AS "Salary"
, e.commission_pct AS "Comission %"
, 'works as ' || j.job_title || ' in ' || d.department_name || ' department (manager: '
|| dm.first_name || ' ' || dm.last_name || ') and immediate supervisor: ' || m.first_name || ' ' || m.last_name AS "Current Job"
, TO_CHAR(j.min_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') || ' - ' ||
TO_CHAR(j.max_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') AS "Current Salary"
, l.street_address || ', ' || l.postal_code || ', ' || l.city || ', ' || l.state_province || ', '
|| c.country_name || ' (' || r.region_name || ')' AS "Location"
, jh.job_id AS "History Job ID"
, 'worked from ' || TO_CHAR(jh.start_date, 'MM/DD/YYYY') || ' to ' || TO_CHAR(jh.end_date, 'MM/DD/YYYY') ||
' as ' || jj.job_title || ' in ' || dd.department_name || ' department' AS "History Job Title"
FROM employees e
-- to get title of current job_id
JOIN jobs j ON e.job_id = j.job_id
-- to get name of current manager_id
LEFT JOIN employees m ON e.manager_id = m.employee_id
-- to get name of current department_id
LEFT JOIN departments d ON d.department_id = e.department_id
-- to get name of manager of current department
-- (not equal to current manager and can be equal to the employee itself)
LEFT JOIN employees dm ON d.manager_id = dm.employee_id
-- to get name of location
23. HOW DOYOU HYDRATE
YOUR DOMAIN OBJECTS?
SELECT
e.employee_id AS "Employee #"
, e.first_name || ' ' || e.last_name AS "Name"
, e.email AS "Email"
, e.phone_number AS "Phone"
, TO_CHAR(e.hire_date, 'MM/DD/YYYY') AS "Hire Date"
, TO_CHAR(e.salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') AS "Salary"
, e.commission_pct AS "Comission %"
, 'works as ' || j.job_title || ' in ' || d.department_name || ' department (manager: '
|| dm.first_name || ' ' || dm.last_name || ') and immediate supervisor: ' || m.first_name || ' ' || m.last_name AS "Current Job"
, TO_CHAR(j.min_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') || ' - ' ||
TO_CHAR(j.max_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS = ''.,'' NLS_CURRENCY = ''$''') AS "Current Salary"
, l.street_address || ', ' || l.postal_code || ', ' || l.city || ', ' || l.state_province || ', '
|| c.country_name || ' (' || r.region_name || ')' AS "Location"
, jh.job_id AS "History Job ID"
, 'worked from ' || TO_CHAR(jh.start_date, 'MM/DD/YYYY') || ' to ' || TO_CHAR(jh.end_date, 'MM/DD/YYYY') ||
' as ' || jj.job_title || ' in ' || dd.department_name || ' department' AS "History Job Title"
FROM employees e
-- to get title of current job_id
JOIN jobs j ON e.job_id = j.job_id
-- to get name of current manager_id
LEFT JOIN employees m ON e.manager_id = m.employee_id
-- to get name of current department_id
LEFT JOIN departments d ON d.department_id = e.department_id
-- to get name of manager of current department
-- (not equal to current manager and can be equal to the employee itself)
LEFT JOIN employees dm ON d.manager_id = dm.employee_id
-- to get name of location
😱
27. MUTABLE STATE 🚫
• Instead of saving the current state, we save the
succession of events that brought us to this state
28. MUTABLE STATE 🚫
• Instead of saving the current state, we save the
succession of events that brought us to this state
• currentState = fold(events, emptyState)
56. READ PATH 👓
View Projector
Event Handler
DB
Event Bus
Events
invoice_id customer balance status
12345 John Doe $12.34 New
67890 Jane Doe $34.56 Sent
57. READ PATH 👓
View Projector
Event Handler
DB
Event Bus
Events
invoice_id customer balance status
12345 John Doe $12.34 New
67890 Jane Doe $34.56 Sent
Payment Added: $12.34
58. READ PATH 👓
View Projector
Event Handler
DB
Event Bus
Events
invoice_id customer balance status
12345 John Doe $0.00 New
67890 Jane Doe $34.56 Sent
59. READ PATH 👓
View Projector
Event Handler
DB
Event Bus
Events
invoice_id customer balance status
12345 John Doe $0.00 New
67890 Jane Doe $34.56 Sent
Status Changed: Paid
60. READ PATH 👓
View Projector
Event Handler
DB
Event Bus
Events
invoice_id customer balance status
12345 John Doe $0.00 Paid
67890 Jane Doe $34.56 Sent
70. PAIN POINTS 😓
• Persisting events and publishing them is not transactional
71. PAIN POINTS 😓
• Persisting events and publishing them is not transactional
• Inherent eventual consistency is not integrated in the
product (read after write)
72. PAIN POINTS 😓
• Persisting events and publishing them is not transactional
• Inherent eventual consistency is not integrated in the
product (read after write)
• Rebuilding views is a complex operation
90. APACHE KAFKA
• Distributed append-only log
• Replicated, fault-tolerant
• Often used as pub-sub or queue
• Used heavily at LinkedIn, Netflix,
Wix and many others
113. STREAM-TABLE DUALITY
User Pageviews
alice 1
User Pageviews
alice 1
charlie 1
User Pageviews
alice 2
charlie 1
("alice", 1)
("charlie", 1)
("alice", 2)
User Pageviews
alice 1
114. STREAM-TABLE DUALITY
User Pageviews
alice 1
User Pageviews
alice 1
charlie 1
User Pageviews
alice 2
charlie 1
("alice", 1)
("charlie", 1)
("alice", 2)
User Pageviews
alice 1
User Pageviews
alice 1
charlie 1
115. STREAM-TABLE DUALITY
User Pageviews
alice 1
User Pageviews
alice 1
charlie 1
User Pageviews
alice 2
charlie 1
("alice", 1)
("charlie", 1)
("alice", 2)
User Pageviews
alice 1
User Pageviews
alice 1
charlie 1
User Pageviews
alice 2
charlie 1
136. STREAM PROCESSING APP
Streams
API
Your app
• Transforms and enriches data
• Stateless / stateful processing
• Supports windowing operations
• Embedded in your app
137. STREAM PROCESSING APP
Streams
API
Your app
• Transforms and enriches data
• Stateless / stateful processing
• Supports windowing operations
• Embedded in your app
• Elastic, scaleable, fault-tolerant
141. PROCESSOR API
• The most low-level
• Interact with state-stores, schedulers, etc.
• All standard operations are implemented like this
(map / filter / …)
• Create your own custom processing logic!
151. KSQL
• SQL dialect for streaming data
CREATE TABLE possible_fraud AS
SELECT card_number, count(*)
FROM authorization_attempts
WINDOW TUMBLING (SIZE 5 SECONDS)
GROUP BY card_number
HAVING count(*) > 3;
200. WINS 🏆
• Simple and declarative system
• Atomicity - Kafka used as event-store + notification
201. WINS 🏆
• Simple and declarative system
• Atomicity - Kafka used as event-store + notification
• Eventual consistency is handled gracefully
202. WINS 🏆
• Simple and declarative system
• Atomicity - Kafka used as event-store + notification
• Eventual consistency is handled gracefully
• Easy to add or change views
205. TAKEAWAYS 🍔
• Event driven systems and event sourcing can help
create very flexible and scalable systems
206. TAKEAWAYS 🍔
• Event driven systems and event sourcing can help
create very flexible and scalable systems
• Know your tradeoffs (consistency, schema evolution,
debugging, error handling, …)
207. TAKEAWAYS 🍔
• Event driven systems and event sourcing can help
create very flexible and scalable systems
• Know your tradeoffs (consistency, schema evolution,
debugging, error handling, …)
• Kafka & Kafka Streams are powerful tools that can be
employed in many use cases