● Home
Knowledge
Action Journal
Homework
Labs
Study
Try
Case
Make
Training
Misc
Encode Cards
Study History
Free Recall
Quizzes
Digests
Tasks
Conjectures
Belief Gym
Audio Timeline
Knowledge Items
People
Templates
Outcomes
DMXs
Situation Analysis
Council Debate
Parable
Branch Learning
Story Engine
Content Engine
Explainer
Rehearsal
Bench
Import Kindle Notes
--Catchfile
$1 tasks and $4 tasks
01 isn't a chat model
1,000 Simultaneous Experiments: The Next Mental State for Founders
10 DFS Secrets From Top Pros
10 Marketing Lessons from Steve Jobs
10 Traits of Massively Successful People
1000 Ways to Make $1000
100M Leads
100M Money Models
100M Offers
106 Response Winning Headlines
10X is Easier than 2X
10X: Metrics for Early Stage Startups
12 Principles of Manufacturing Excellence
12 Winning Strategies For Executives With ADHD
13 Habits of Smart Risk Takers
20 Rules of Knowledge Formulation
2020 Shareholder Letter
2023 RUFF Planning
2024 Life Handbook
2025 Fantasy Football Impact: New Coaches
2026 Fantasy Prep
2026 Fantasy Prep: Fantasy Points
2026 Fantasy Prep: Late Round Sleepers
2026 Product Strategy Vision & Initiatives
2026 Productivity Stack: Sleep, Hormones, & Supplements - Dr Andrew Huberman
2026 Reading List
2x2 Matrix
33 Strategies of War
48 Laws of Power
4P Copywriting formula
50 Things I Know
500 Ways To Deliver Value
51 questions you can ask in any meeting when you are "not getting it"
55 Business Model Pattern
7 Habits of Highly Effective People
7 Key Metrics Every Business Should Track
7 Powers
7 Rules of Power: Surprising--but True--Advice on How to Get Things Done and Advance Your Career
76ers
78 (Unwritten) Rules Of Life
80/20 Principle
9 Rules of Debugging
92 Tricks: How To Talk To Anyone
97 Things Every Data Engineer Should Know
A Career Cold Start Algorithm
A Dozen Things I’ve Learned from Charlie Munger about Moats
A Good Explanation for Long Term Behavior
A LIFETIME OF SYSTEMS THINKING
A Mind for Sales
A Portfolio of Small Bets
A Practical Guide to Building an Event Streaming Platform
A Practical Guide to Executive Presence
a16z
a16z Reading Lists
a16z Sales
Aaron Levie
Accelerated Expertise
Acceptance and Commitment Therapy
Account Executives
ACG: Confluent Certified Developer for Apache Kafka
Achieving a World-Class Standard
Acquisition Reports
Actioning Towards PQOs
Active Inference
Activity Heuristic
Adam Townsend
Adding is favored over subtracting in problem solving
ADHD
ADHD 2.0: New Science and Essential Strategies for Thriving with Distraction
Adjacent Possible Theory
Adora Cheung
Advanced Arithmetic
Advisors
Aesthetic Digital Taste
Afghanistan
Agent Command Centers: Scaling Human Attention
Agent of Influence
Agent Skills API Doc
Agentic Systems, Deployment, and Enterprise Integration
Aggelos Mouzakitis
Aggregation Theory
Agile
Agile Strategy
AI Agents
AI agents and problem prioritization
AI and the Future of Coding
AI Model Inference Economics
AI Native Constraint Training
AI Native Platitudes
AI/ML Use Cases
AIP: Services Estate
Air Fryer Recipes
AirBnb
Akira Kurosawa
Alan Carr's Easyway Method
Alan Watts
Alasdair MacIntyre
Albert Einstein
Alchemy
Alex Hormozi
Algorithms of Thought
Algorithms To Live By
Allocation As a Theory
Alpine.js
Alternatives to OKRs
altMBA Prompts
Always Day One
Amanda Robson
Amazon Leadership Principles
Amazon Q and Amazon Quick
Ambient Agents
Amp it Up
Amplifying Contextual, Analogical, Temporal, and Counterfactual Intelligence
Amy Dunford
Amy Hoy
AMZN Shareholder Letters
An MBA for Business Operators
Analysis + Capital + Execution
Analytics
Analytics and Shift-Left
Anatomy of a League Winner
andrej karpathy on learning
Andrew Chen
Andrew Huberman
Andrew Wilkson
Andy Grove
Annie Duke
Anticipatory Affect
Antifragile
Antonio Damasio
Apache Avro
Apache Iceberg
Apache Kafka
Apache Kafka and API Management / API Gateway – Friends, Enemies or Frenemies?
Apache Kafka Background
Apache Kafka Definitive Guide
Apache Kafka Docs
Apache Kafka Fundamentals Course
Apache Kafka on Confluent Cloud Best Practices
Apache Kafka Survival Guide
Apache Kafka Use Cases
Apache Kafka vs. Enterprise Service Bus
Apache Pinot
Apache Samza
API Gateways
Apify
Application Integration
Applying Power Laws to Execution
Are there any good principles/formulas to follow when selecting a new business name?
Are there any good principles/formulas to follow when selecting a new business name?
Are You Solving The Right Problem
Are you stuck in movie logic?
Arguments
Aristotle
Aristotle's Three Appeals
Art of Nonfiction
Artificial Intelligence
Ashish CCDAK Notes
Asimov and Emotion
Ask More
Ask Your Developer
Async/Await
ATG Knees Over Toes
Atomic Habits
Augmented Intelligence
Austin
Automation
Automation Theory
Autopoiesis
Awaken The Giant Within
AWS
AWS Analytics
AWS Certified Generative AI Developer - Professional
AWS Databases
AWS Generative AI By Layer
AWS Generative AI By Service
AWS Generative AI By Service: Application Integration
AWS Generative AI By Service: Machine Learning
AWS Generative AI By Service: Management and Governance
AWS Generative AI By Service: Security, Identity, and Compliance
AWS in One Line
AWS Lambda Sink Connector
AWS Networking
AWS Professional Architect Study Guide
AWS Storage
Ayesha Selden
Balanced Scorecard
Barbara Minto
Basic Economics
Basketball
Batch and Stream Processing in Confluent Cloud for Apache Flink
Bayes Theorem
Be Odysseus
Be Slightly Evil
Be Your Future Self Now
Becoming a better writer
Becoming Data Driven in Business: A Series
Behavior Change Management
Behavioral Game Theory
Being That Guy
Benzodiazephine
Best Practices for Driving Projects to Completion
Best Ways to Build Better Habits & Break Bad Habits
Better Harness: A Recipe for Harness Hill-Climbing with Evals
Better Problem Selection
Beyond the Sales Process
Biased Competition Theory
Big Bet Leadership
Big Data is Dead Long Live Easy Data
Binstack: Making a maximal multi-dimensional decision
Biofabrification
Biology
Bipul Sinha
Bird By Bird: Some Instructions On Writing And Life
Bitcoin
Black Box Machine Learning
Blake Emal
Blitzscaling
Blockchain
Body Language
Book of Hooks
Books
Bootstrap Grid System
Bootstrap Spacing Utilities
Bottlenecks vs. Bandpass
Boz
Brag Book
Brainstorming New Startup Ideas
Brainstorms / Action Journals
Branding Pays
Brandolini's Law (The Bullshit Asymmetry Principle)
Breakthrough Advertising
Brian Johnson
Brian Kehm
Brian Roemmele
Buffett & Munger – A Study in Simplicity and Uncommon, Common Sense
Build
Build a Prompt Learning Loop
Building Agents That Automate Knowledge Work
Building agents with the Claude Agent SDK
Building Event-Driven Microservices
Building LLM applications for production
Building Microservices
Building New Intelligence Agents
Business Engineering Blogs
Business of Blockchain
Businesses considered essential
Butter
Byrne Hobart
Cache Layer Architecture
Calixa
Calvin Rosser
Capital Allocation
Capital Allocation Guide
Careers
Careers in Sales
Carmen Medina
Cate Hall
Category Errors
Category Errors of AI/Agents
CCEL (Confluent Customer Engagement Lifecycle)
CeCe Moore
Certain to Win
CFLT Work Journal H2
Chamath Palihapitiya
Change Data Capture
Change Management
Change Management & Governance
Charisma University
Charles Blog Notes
Charlie Munger
Charlie Munger
ChatGPT
Checklist Manifesto notes
Chess
Chicken Recipes
China
Chip Wilson
Chris Orlob
CLA Constraints-Led Approach for Learning
Claude Code
Claude Skills
Clickhouse Connector
Clickstream Analysis
Cloud Consumption Playbook
Cloud KPIs
Cloud Migrations
Cloud Native Transformation
Cloud SDR Management Projects
Cloudflare
Clusterr Framework
Co-Intelligence
Co-opetition
Coffee v. Kale JTBD
Cognitive Appraisal
Cognitive Behavioral Therapy
Cognitive Biases
Cognitive Flexibility
Cognitive Reappraisal
Cold Calls
Color Combinations for Glyvian
Comedy
Common Tasks
CommonCog
Communicating with Agency
Communication
Communication is The Job
Communication Scripts
Communication Swipe File
Company
Company Proprietary Prompts
Comparing Apache Flink with Confluent Cloud for Apache Flink
Competitive Intelligence Toolkit:
Completion as a Crucial Dimension of Stepping Stones
Compound/Robust Engineering
Compressed Skill List
Compute and Containers
Compute Pools in Confluent Cloud for Apache Flink
Computer Programming
Computing
Confluent
Confluent 2024 Evolving Customer Growth GTM
Confluent 2024 Strategy
Confluent 2026
Confluent Big Bets
Confluent Cloud
Confluent Cloud Case Studies
Confluent Cloud Client/Service Quotas
Confluent Cloud Customer Success Engineering
Confluent Cloud Demo
Confluent Cloud Docs
Confluent Cloud Flink Autopilot
Confluent Cloud Onboarding Checklist
Confluent Cloud Pricing
Confluent Cloud Support Guide
Confluent Connectors
Confluent Customer Success and Expansion Strategies
Confluent Event Driven Microservices Whitepaper
Confluent FY24 Sales Comp
Confluent Intelligence 2026 Strategy
Confluent Land Motions
Confluent Mastery
Confluent MEDDPICC Guidebook
Confluent PAO LTV 2021
Confluent PG Metrics
Confluent Platform
Confluent Product Roadmap
Confluent Replicator
Confluent Salesforce Connectors
Confluent Solutions
Confluent Tools (K8s + Ansible)
Confluent Win Wires
Connect
Connect and CDC
Consilience
Context Engineering for AI Agents: Lessons from Building Manus
Context Graphs
Context Management of Big 3 Model Providers
Conversation Code
Conversation Code Revised Notes
Conversation-Driven Development
Conversational Kumite
Conversationalist
Cooking
Cooking Lasagna
Copywriting
Core Ideas of the Progress Principle
Cost & Capacity
Cost Optimization, Performance, and Observability
Costly Signaling Theory
Courage Equals Freedom w/ Claude
Courage To Be Disliked
COVID19
COVID19 Pandemic
Creating a Wraith
Creating Good Process
Creating great SAAS Landing Pages
Creating Sales Urgency
Creating Scaffolds with Laravel
Creating the Accountable Organization
Creating the Accountable Organization Guidance
Creating The Ultimate Odysseus System
Creative
Creative Selection
Criminal Profiling
Critical Chain Project Management
Critical Realism
Critical Thinking
Crucial Conversations
cSDR Cheatsheet
cSDR Demos
cSDR Discovery Playbook
cSDR Experiment
CSFLE
Culture Hacker
Culture Virusees
Cupid Investing
Curie Medical Demo Script
Curie Medical Presentation Script
Curves
Customer Experience Job Maps
Customer Job Deep Dive
Customer Retention
Cut your fantasy football drafts into quarters
Cybernetics
Cycle Time
Dad Jokes
Daily Intention Review
Dailys
Dakota Robertson
Damn the Org Chart
Dan Koe
Daniel Dennett
Daniel Disney
Daniel Gross
Danny Postma
Dashboards, Custom Reports, Intelligence Alerts
Data Driven Management
Data Driven Organizations
Data Driven Startups
Data Integration
Data Locality
Data Management at Scale
Data Mesh
Data Visualization
Data-Driven Businesses
Databricks
Databricks and Premier Inc
Databricks for Executives
Databricks for HLS
Databricks for Retail
Databricks Presentation Round Prep
Databricks Solutions Architect Interview
Databricks Solutions Design Interview Prep
Databricks Solutions Design: Consultative Layer
Databricks Solutions Design: Governance & Security
Databricks Solutions Design: Integration
Databricks Solutions Design: Serving & Consumption
Databricks Solutions Design: Sources & Ingestion
Databricks Solutions Design: Storage
Databricks Solutions Design: Transformation & Processing
Databricks Solutions Design: Whiteboard Tips
Databricks Vibe Coding Interview Prep
Databricks: Build, Pitch, Demo! - Curie Medical
Date Night Ideas
Dave Kline
David Goggins
David Ogilvy
David Senra
David Shippins
David Skok
Death is the Default
Decide To Play Great Poker
Decision Elimination: The Largest Hidden Lever on Transaction Cost
Decisions as Bets
Deep Agents
Deep Dive Into Marketing Channels for Indiehackers/Micropreneurs
Deep Learning
Deep Learning Basics
Deep Thinker
Deep Thinkers
Deep Thinking Books
Deglazing
Delivery Guarantees and Latency in Confluent Cloud for Apache Flink
Demand Based Marketing
Demand Curve
Demand Side Problems
Demand-Side Sales
Denise Shull
Descartes' Error
Design Principles: Feedback
Designing Data-Intensive Applications
Designing Event Driven Systems
Designing for Buying: How to Break Through the Ceiling in Product-Led Growth
Designing the Ideal Bootstrapped Business
Detailed Daily Manifestation Practice
Determinism in Continuous Queries on Confluent Cloud for Apache Flink
Developer Advocacy
Developer Productivity Startups
Developer Tools, Migration, and Customer Engagement
Devin Reed
DFS
Dickie Bush
Digital Business Solutions
Digital Ocean
Dinesh Raju
Directed Acyclic Graphs
Disarm, Elicit, Influence, Ask
Discipline Equals Freedom
Disciplined Risks
Discovery Calls
DiSSS Method
Distributed Systems
Ditch Your To-Do List. Use these Docs To Make More Impact
Docker
Doing Time Right
Domain Driven Design
Domains that are difficult for AI
Don't Create Chaos
Don't Flinch Manual
Don't Think of An Elephant
Don’t Build Multi-Agents
Donald Polkinghorne and Narrative Knowing
Dopamine Reward Prediction Error Visualization
Double-Loop Learning
Doublespeak
Douglas Engelbart's NLS/Augment System
Draftkings
Drift
Drift Script
Driven by Compression Progress
Drugs/Supplements
DSP 2 Year Plan
Duplicate Records in Tableflow
Dynamic Verbal Confirmation Prefixes
E-commerce
E-Myth Revisited
Easy Way To Stop Smoking
Eat That Frog
Economics
Economists Numbers Guide
Ed Thorpe
Effective Kafka
Effectual Entrepreneurship
Efficiency: Optimizing Thought and Action
Efficient Model Validation and Calibration Strategies
Electrical Engineering
Elements of a Story
Elena Verna
Elon Musk
Elon's Algorithm
Eloquent Query Scopes
Eloquent Relationships
Emotional Memories
Emulsion
Ender's Game
Endure
Engineering Teams
Epistemology
Eric Thomas
Escaping the Build Trap
ETL
ETL v ELT
Eurie Kim
Evaluation & Feedback
Evaluation, Quality Assurance, and Troubleshooting
Event and Data Modeling
Event Driven Microservices
Event Tracking
Every time I have a startup idea a few days or weeks later someone else is doing it. Are there any ideas left?
Everyone talks about Clawdbot, but here's how it works
Evolutionary Psychology
EX-Livewire
EX-Tailwind
Example Script
Excuse Log
Execution as StarCraft
Execution as StarCraft w/ Claude
Executive Functioning
Executive Functions: What They Are, How They Work, and Why They Evolved
Exercises with most muscle activation
Expected Value
Experimentation / Testing
Explaining Creativity
Exposure Therapy
Expressing Ideas Through Code: A Working Guide
Extraction, Sanitization & Normalization
Extreme Asking
Extreme Giving
Extreme Integrity
Extreme Networking
Extreme Ownership
Extreme questions to trigger new, better ideas
Extreme Urgency
Extremely High Agency
Fanatical Prospecting
Fantasy Football
Fantasy Football 2021
Fantasy Football 2024
Fat
fathersabrina
Favorite Reframes
FB Libra
Feel The Fear and Do It Anyway
Feel The Fear Notes
Feeling and Knowing
Fela Kuti
Fermi Estimation Problem Solving
Fermi Estimations
Figma's Dylan Field on the Future of Design
Financial Intelligence for Entreprenuers
Finish
Finish What You Start
Finite and Infinite Games
Fintech Tools and APIs
First Principles: Why Assistance Expands Effectiveness
First Round Articles
FiveThingsYouDidToday
Fix Your Broken Team: 8 Turnaround Tips for Sustained Success
Fixed-point futurism
Fixing the ROI rubric
Flink Overview
Flink SQL Statements in Confluent Cloud for Apache Flink
Flip The Script
FlockJay
Flow
Fluid Intelligence
Focus
Focus on Impact
Focused Work Sessions
Focusing: The Essence of Change
Football Practice Structure and Organization Principles
Foreign Keys
Forget The Funnel
Formal definitions of Pills (Red, White, Blue, Black)
Foundation Model Integration, Data Management, and Compliance
Foundation Model Integration, Data Management, and Compliance: Services
Foundation Training: From Pain To Performance
Four Disciplines of Execution
Four Disciplines of Execution Summary
Four Thousand Weeks
Frameworks
Framing
Frank Slootman
Frankfurt's Theory of Higher-Order Desires
Friedrich Hayek
From Impossible To Inevitable
Full Catastrophe Living
Function to get a url query value
Functional Programming
Fundamentals of Data Engineering: Plan and Build Robust Data Systems
Funny Worldstar
Future Me
Future Me Future Statement List
Future Me Present Statement List
Future Me v2
Future Me v3
Future Me v4
Future Me: Conditional Rules
FY2026 SE Accounts
FY26 Faire
Fyodor
Fyodor v2
Gadamer's Hermeneutic Circle
Game Theory
Games People Play
Gap Selling
Gartner's 13 Sales Plays for Account-Based Everything
Gary Becker's Human Capital Framework
Gary Klein
Gary V
Gena Gorlin
General Marketing
General Planning Dilemma
General Principles of Resistance Training
Generative AI
George Soros
Gestalt Psychology
Get It Done
Get The Truth
Getting Shit Done
Getting the most out of superintelligent ai models
Git
Global Economy and Politics
Gnosis
Gnosis
Goals and Ecommerce
Goals, Methods, Habits (GMH)
Going slow to go fast
Goldratt's Rules of Flow
Good Players Are Still Good: The Most Undervalued Wide Receiver Metric
Good Publications
Good Reasons for Bad Feelings
Good relationship questions
Good Strategy, Bad Strategy
Good To Great
Google Analytics Breakthrough
Google Fonts I like
Google Tag Manager
GPT 4.1 Prompting Guide
GPT-3
Graham Weaver
Graph Theory
Great Founder Theory
Great Writing Examples
Greatness Cannot Be Planned
Greg Brockman
Grooming
Growth and Stock Prices
Growth Engineering
Growth Is Now a Trust Problem
Growth Mindset
GTD
GTM Engineering School
GTM Tactics for Sales Acceleration
GTM Tactics for Sales Acceleration x ChatGPT
GTM Tactics for Sales Acceleration x Claude
GTM Tactics for Sales Acceleration x Grok
Guidelines to Problem Solving and Decision Making
Gwen Shapira
Hacking Growth
Hacking Sales
Hammock Driven Development
Handbook of Emotion Regulation
Handling and Manipulating Dates and Time in PHP
Hardball
Hardcore Claude Code (Reddit Post)
Harold Adams Innis
Harry Dry
Haruki Murakami
Harvard Business Review
HBM Industry Michael Porter
Heidegger's Concept of Disclosure
Helpful Tips
Henrik Wærdin on Agents and Portfolio Entrepreneurship
Hero's Journey
Heroes
Heuristics
Hey Whipple, Squeeze This: The Classic Guide to Creating Great Advertising
High Contact Work
High Growth Handbook
High Output Management
High Performance Habits
High Quality Investing Content Sources
High Scalability Blog
Home Ownership
Horizontal Scaling With Docker
Hosts file Block list
How Big Things Get Done
How Brands Grow
How Brands Grow: What Marketers Don't Know
How Builders Think
How can I accelerate my personal growth efficiently?
How can I come up with a marvelous idea (in technology or finance)?
How Claude Code Works
How Computer Programs Work
How email verification works
How Finance Works
How Is the ADHD Brain Different
How Kafka and Flink Can Support AI Agents
How NFL Offenses Are Adjusting in 2025
How Not To Be Wrong: The Power of Mathematical Thinking
How Note Taking Can Help You Become an Expert
How to Ace an Internal Interview
How to analyze the elements of a game
How To Ask Questions The Smart Way
How to attend an event/meetup
How to automate, delegate, or defer
How to avoid hubris
How to be a high-value man
How To Be a Power Connector
How to be a tech evangelist
How to be an effective human
How to Be Great? Just Be Good, Repeatably
How to be more agentic
How To Be The Funniest Person In Any Room
How to Become Data Driven
How to boil life's hardest questions down to just 12 rules
How to Build an Agent
How to Build an Effective Developer-Focused Product-Led Growth Team
How to build great products
How to Build Relationship with Sales Leads
How to center content vertically using bootstrap
How to classify games
How to clean teeth effectively
How to clean the kitchen
How to clear DNS cache on Mac
HOW TO COMMUNICATE WHEN TRUST IS LOW (WITHOUT DIGGING YOURSELF INTO A DEEPER HOLE)
How to complete a work break stretch
How to configure a domain from Godaddy to Netlify site
How to connect a domain to server
How to Convert Trials into Customers
How to create a game theory matrix
How to create a laravel request
How to create a new FailMachine Site
How to create a product vision
How to create a simple Select2 select input
How to create an alias in bash profile
How to create better outcomes
How to create OKRs
How to Create Successful Sales Plays and Playbooks
How to create your own Jquery plugin
How to debug a PHP script
How to debug slow queries
How To Decide
How to decide whether or not to quit something
How to design a automation solution
How to do a jobs map
How to do an ajax request using Vue
How to do customer development
How to do JTBD prototyping
How To Evaluate AI Tools
How to Figure Out What You Really Want
How To Find A $1 Million Idea — What Product To Build
How to find a single item using Eloquent
How to find an item in a javascript array by a value
How to get a nutty flavor
How to get cheap flight tickets
How to get just column names from MySQL table
How to get random elements using Eloquent
How to get startup ideas
How to get the current window location
How to get time between two dates PHP
How to Get Whatever You Want
How to get your ida.rsa.pub
How to Get Your Organization to Appreciate Apache Kafka
How to handle conditional rendering in Vue
How to handle events with your Vue component
How To Have a Good Day
How to have a robust database
How to horizontally align content using bootstrap
How to identify frame traps
How to identify the elements of a game
How to implement a Vue component
How to implement an AJAX form submission using Vue
How To Impress Someone You Admire
How to influence people to behave how you want them to
How to influence yourself
How to instantly be better at things
How to kill crab grass and clover
How to kill slow and stuck queries
How To Know Everything
How to make a sidebar with Bootstrap
How to make a sound design decision
How to make decisions
How to Make it to President's Club in 2025
How to manipulate JSON in Javascript
How to market effectively
How To Measure Anything
How To Meet New People Guidebook
How To Operate
How to operate effectively in this world
How To Pick The Right Tools
How to protect yourself from analytics hacking
How to purposefully read a book
How To Queue Work For Greatness
How to quickly launch software projects
How to react when you see someone has built something that you had an idea of building
How to resolve to the correct directory Path
How to Rise from Newbie to General Manager in Seven Years
How to run a masterful product company
How to run PHP Unit via Docker-compose containers
How to run SQL queries in Laravel
How to select high leverage activities to work on
How to sell to a problem
How to sell to developers
How to serve a server with php artisan
How to Set 2025 Goals That Are Impossible to Fail
How to Set up Time Synchronization on Linux Ubuntu
How to share a blog post after writing it
How to sharpen a knife
How to ssh into laravel forge
How to start a Jigsaw project from a repo locally
How to start a new FailMachine project locally
How to start a new Laravel Spark project
How to start a work session
How To Style a Page
How to think and be contrarian
How to think from first principles
How to track user behavior using Javascript
How to Train a Wild Elephant
How to troubleshoot bootstrap grid system
How to troubleshoot common Vue problems
How to troubleshoot SASS errors
How to troubleshoot solving riddles
How to troubleshoot the design process
How to turn a PHP object into an array
How to turn an idea into a working UI
How to understand the natural world
How to use a seeder and seed data in Laravel
How to Use AI to Create Role-Play Scenarios for Your Students
How To Use Docker Swarm for Horizontal Scaling
How to use Eloquent mutators
How to use Quilljs editor
How to use the Intrigue frame
How to use the javascript fetch API
How to use the Prize frame
How to use timezones with mysql
How to use webpack to implement Vue Modules
How to version control effectively with Git
How to wake up in the morning
How to Win in a Winner-Take-All World
How To Win Violently
How to win, constantly and consistently
How To Work Better With AI, Through AI
How to work hard
How to write
How to write a bash script
How To Write Clean and Effective Code
How To Write Funny
How to Write Nurture Emails
How volatility affect the price of options
Howard Marks
HTTP Headers
HTTP Methods
HTTP Status Codes
Hubspot
Hubspot Sales Email Subject Lines
Human-Computer Interaction (HCI) Principles
Human-LLM Collaborative Reasoning Techniques
Humble Modifiers
Husserl's Concept of Bracketing (Epoché)
HV Action
HVAC
IBM
IBM WatsonX
Identifying a niche between Information/AI/Data/Automation
Identifying Your Most Important Outputs
Identity & Entitlement
Identity Economics
If Only We Knew What We Know: The Transfer of Internal Knowledge and Best Practices
If you over salt your food
Igbo
Images of an Organization
Impatience
Implementing CRMs
Important Info
Important Information
Impossible To Inevitable
Impro
Improv Wisdom
Improving ChatGPT Prompts
Improving Predictive Processing Systems
In Defense of Radical Self-Betterment
Inadequate Equilibria
Inbox Ten
Incentives and the Cobra Effect
Increateable
India
Individuals as Magnitudes and Teams as Vector Sums
Inertia.js
Inference is the New Sales & Marketing Spend
Infinite Games
Influenza
Information and the Modern Corporation
Information Processing Models
Information Rules
Information Rules: A Strategic Guide to the Network Economy
Information Theory
Inner Game of Work
Inside OpenAI’s in-house data agent
Inspirations
Installing QuillJs
Instapage
Intellectual Humility Is a Cop-out
Intellectual Tapestry
Intellectual Truths
Intelligence Products
Intelligence tradecraft and Humint
Intercom Creating Best Practices
Intercom Customer Support Best Practices
Interesting Deep Learning Projects
Interesting GA segments for SAAS
Internal Training
Internet Pipes
Interviewing
Introduction to the Sales and Marketing Machine
Introductory Sales Questions
Intuition Pumps and Other Tools for Thinking
Inverse, Converse, Contrapositive
Investing
Irrational Fears
Is Covid19 a Synthetic Virus?
Itamar Turner-Tauring
J.L Austin Speech Act Theory
Jack Butcher
Jacob O'Bryant
James Gross's Process Model of Emotion Regulation
James O'Shaughnessy
Japan
Jason Capital
Jason Cohen
Jason Liu
Jasper Polak
Javascript
Javascript Array Manipulation
Javascript Tricks
Jay Kreps
Jean-Paul Sartre
Jeff Bezos
Jerry Seinfeld
Jess Chan
Jev
Jia Jiang
Jim Dethmer
Jim Rogers
Jim Rohn
Joanna Wiebe
Job Hunt
Job Stories
Jobs To Be Done Theory
John Anthony
John Cutler
John McMahon
Jordan Belfort
Jordan Peterson
Jordan Peterson
Josh Braun
Josh Brindley
Josh Waitzkin
Josh x Keenan
Journal
Journey to Event Driven
Jquery
JTBD Insight Framework
Julian Shapiro
Julie Gurner
Just Demo
Justin Skycak
Justin Welsh
Kafka Connect
Kafka Consistency and Availability
Kafka Integrations
Kafka Internals
Kafka Streams
Kafka Streams DSL
Kafka Topic Design
Kafka Value Props
Kafka, CDC, ML/AI, Agents
Kafka, CDC, ML/AI, Agents: Source
KafkaJs
Kage Bunshin
Kai Waehner
Karisma King
Katherine Boyle
Kathy Sierra
Keith Rabois
Keith Stanovich and Rational Thinking
Kelly Criterion
Keras
Kevin Samuels
Key Takeaways from Elite Athlete Mental Research
Kieran Klaasen on Agentic Workflows
Kieran Majury
Knoster Model
Knowledge Management
Knowledge through Syllogistic Reasoning
Kolb's Experiential Learning Cycle
KSQL / ksqlDB
ksqlDB Concepts
ksqlDB Tutorials
Kubernetes
Kubernetes Docs
Kubernetes Tutorials
Kumar Thangudu
Kunal Shah
Labor Productivity
LangChain
Laravel
Laravel Collections
Laravel Factories
Laravel Forge
Laravel Installation
Laravel Queues
Laravel Request Instance
Laravel Tinker
Laravel Web Push Notifications
Larry Long Jr.
Latent Expertise: Everyone is in R&D
Laws of Fun
Laws of Thermodynamics
Lead Review Memo
Leaders as Decision Architects
Leadership Handbook
Lean Canvas
Lean Customer Development
Lean Customer Development
Lean Startups
Learning from Quant Traders
LearnUI
Lessons from Building AI Agents for Financial Services
Lessons from the 2022 RUFF League
Letters to a Young Poet
Levels of an Idea
Levels of Done
Lex Freidman
Life Coaching Handbook
Life Satisfaction
Limits of Service Leadership
Lincoln Tunnel
LinkedIn Playbook
Linux
Linux Certification Training
Linux Certifications
Linux Commands
List of CFJ in business
Listening Is The Job
Lived Experiences
Livewire
Living with Complexity
LLM Codegen Workflow
LLM Prompts
Load Balancers
Loading QuillJs Data into a form
Local Deployment Concepts for LLMs
Logic
Logic [Fixed]
Logic of Failure Compressed Notes
Logical Fallacies
Long Story Short
Longform Lite
Loose Podcast Ideas
Loserthink
LSAT Reading Comprehension
Ludwig
Lux Capital
Mac and Cheese
Machine Learning
Machine Learning Algorithms
Machines of Loving Grace
Mailchimp
Make
Make a Name For Yourself
Make it Clear
Make Time
Making Work Visible
Manage your Day to Day
Manage Your Manager
Managed Databases
Management
Management and Governance
Management Consultancy
Management Levels and Types
Manager Superpower Series #2: Optimism
Managing Your Manager
Mapping Semantical Certainties to Kelly Criterion Edge Percentages
Maps of Meaning
Marc Adler
Marc Andreessen
Marcus, Friedrich, Fyodor
Mark Manson
Market Mind Games: A Radical Psychology of Investing, Trading and Risk (DIGITAL AUDIO)
Marketing
Marketing Segmentation
Marketing to Developers
Martin Kleppmann
Marty Lobdell
Master Data Management
Master Shi Heng Yi
Mastering Confluent Kafka from Failure Cases
Mastering Fun as a Skill
Mastering Kafka from First Principles
Mastering Kafka from First Principles: API-First/Digital Native
Mastering Kafka from First Principles: Fintech
Mastering Product Experience in SaaS
Mastering Technical Sales
Math
Mathematics
Matrix
Matt Abrahams
Matt MacInnis
Matt Mochary
Matt Pocock on Speaking
Matthew Confer
Max Levchin
Maxims For Thinking Analytically
Maybe you’re not Actually Trying
McKinsey
Measuring Deal Momentum, the Formula for Enterprise Sales
Mechanism Design
Meditation
Meditations
Meditations Guidance
Melvin Conway
Memory as Reasoning
Memos
Mental Action Tricks
Mental Models of Analytics
Meta-Lessons from Creating with Claude
Metabolism
Metadata
Metaphors We Live By
Method Acting
Michael Mauboussin
Microservices
Microservices with Apache Kafka
Microsoft on Microservices
Mid-funnel (MOFU)
Midwit
Migration
Milestones, Learnings, Confidence, the inner triangle to Outcomes, Knowledge, Actions
Million Dollar Weekend
Millionaire Fastlane
Mimetic Theory
Mind Magic
Mindfulness and Rationalization
Mindsets on being Relentless by Tim S. Grover
Minimalist Web Design
Minto Communication Guide
Minto Pyramid
Mircea Eliade's Eternal Return
Mission, Strategy, and Tactics
Model Customization
Model Evaluation Fundamentals
Model Invocation
Model Serving
Model Thinkers
Models of Misalignment, Information, and Coordination
Module 0: The Agent Platform Landscape
Module 1: The Token Economy
Module 1: Agent Fundamentals: Loops, Reasoning, Memory, and State
Module 1: Evaluation Foundations
Module 1: The GenAI Threat Model
Module 2: Evaluation Datasets and Ground Truth
Module 2: Input Safety and Adversarial Defense
Module 2: Requirements elicitation for generative AI
Module 2: Token Efficiency and Context Optimization
Module 2: Tools, Function Calling, and MCP
Module 3: Automated Evaluation and Model Comparison
Module 3: Data Validation and Processing Pipelines
Module 3: Model Selection, Tiering, and Distillation
Module 3: Multi-Agent Systems and Model Coordination
Module 3: Output Safety and Deterministic Transformation
Module 4: Grounding, Verification, and Hallucination Control
Module 4: Agent Safety, Control, and Human-in-the-Loop
Module 4: Caching
Module 4: RAG and Retrieval Evaluation
Module 4: Vector Store Design
Module 5: Agent Evaluation
Module 5: Data Security and Network Isolation
Module 5: Model Deployment and Serving
Module 5: Retrieval Mechanisms
Module 5: Throughput, Capacity, and Scaling
Module 6: FM API Integration and Real-Time Delivery
Module 6: Human Feedback and Annotation
Module 6: Latency Optimization and Inference Tuning
Module 6: Privacy and PII Handling
Module 6: Prompt Engineering and Governance
Module 7: Continuous Evaluation, Quality Gates, and Reporting
Module 7: Enterprise Integration, Security, and Compliance
Module 7: Governance, Lineage, and Auditability
Module 7: Resilience, Customization, and Lifecycle
Module 7: Retrieval and Vector Store Performance
Module 8: Developer Experience, Observability, and Troubleshooting
Module 8: Observability, Monitoring, and Troubleshooting
Module 8: Responsible AI: Transparency, Fairness, and Policy
Module 8: Systematic Troubleshooting: Content and Integration
Module 9: Prompt and Retrieval Troubleshooting
Mogul
Mogul: Books
Molly Mielke McCarthy on finding special people, agency, and playing your own game
Mom's Stew
Money Skills Review
Monthly Content Plan
MoonShots mission statement
Moral Reasoning
Morgan Housel
Morgan J. Ingram
Move First Think Later
MrBeast
Multi-tenacy Laravel
Multi-tenancy Software Architecture
My Big 5 Personality Test
My Personal Archtype
My Personal Jungian Analysis
My Philosophy
MySQL
MySQL Antipatterns
MySQL Design Patterns
MySQL How to get the difference between two dates
Mythical Man Month
n8n
Narrative Psychology
Narrative Writing Structure for Internet Content
Narratives, Commitments, Tasks (NCTs)
Naruto
Nat Friedman
Naval Ravikant
Navan Q1 FY27 Earnings Transcript
Navigator
Negative Feedback
Negotiate Vested Options Severance
Neo Sub Factors (Big 5 Personality Test)
Neo4J
Neurobiology of Social Behavior
Neuroscience
Neuroscience of pain and discomfort
Never Eat Alone
Never Finished
Never Split The Difference
New York City
Nick Huber
Nick J Sheriff
Nicolas Cole
Nicotine
Nigeria
Nikita Bier
Nine Things Successful People Do
No Vibes Allowed: Solving Hard Problems in Complex Codebases
Non Linear Dynamics
Non-Negotiable as a personal decision making strategy
Note on the Relationship Between Artificial Intelligence and Human Intelligence
Notes on Managing ADHD
Notes on Managing ADHD
Nothing Fucking Matters
NPS
Nuclear Energy
Number Quest
Numbness To Loss
Oatmeal Raisin Bar Cookies
Oatmeal Recipes
Object Design Style Guide
Objection Handling
Observability
Obviously Awesome
Ogilvy on Advertising
Ole Peters
Olive Oil
On becoming competitive when joining a new company
On Being an Executive
On Being Contrarian
On Culture
On First Principles Thinking
On Humility
On Reviews
On Sarcasm
On the Shortness of Life
On the viability of PQOs Memo
On Writing Well
One-shot Rapid Application Development
Ontology
OODA Ontology
OpenAI
OpenAI API
Operating Efficiently at Scale
Operational Troubleshooting
Operations
Operations Research
Operator
Operator: Books
Opportunity Cost
Optimal Scaling Path for a PHP CRUD Application
Optimizing Eye Health
Orchestration & Control
OSINT
OSS Migrate
Outbox Pattern
Outcome Orientation as a Cure for Information Overload
Outcomes
Overcoming The Time Sink of Getting Blocked
Overfitting
Ownership and Entitlement
Parakeet
Pareto Efficiency
Parmita Mishra
Pat Grady
Path Dependency
Patrice O'Neal
Patrick Mckenzie
Patrick O'Shaugnessy
Patrick Winston
Pattern Breakers
Pattern Recognition for Intelligence Products
Paul Graham
Pavel Training Methods
Paying attention to complete a task/goal
PayPal
Percent Increase Forumla
Perceptual Control Theory
Personal Anki Categories
Personal Office Communication Guide
Personal Reframes
Personal Shadow Work
Personality
Personality Plus at Work
Personality: A User's Guide
Persuasion
Peter Attia
Peter Thiel
pgVector
PHP isset vs empty vs is_null
PHP: How to search for the array key that has the value OR has the array with the value
Physics
Picking Problems to Marry
Pieter Levels
PIPE Program 2025
Pipeline Cures All
Pitch Anything
Pixar's 22 Rules of Storytelling
Planning v. Feedback
Playing Long Term Games
Playing To Win
PM Communication Scorecard
Podcast loose ideas
Poor Charlie's Almanack
Pork
Portfolio Management
Post Ideas
Post-Click User Experience
Practical Change Data Streaming Use Cases with Apache Kafka & Debezium
Practical Use Cases for Read-Only Iceberg Tables
Pragmatics
Pragmatism
Precious Metals
Precommitment: A Way around Temptation
Primal Intelligence: You Are Smarter Than You Know
Principal-agent problem
Private Networking with Confluent Cloud for Apache Flink
Problem Solving
Problem Solving Techniques
Process Data with Flink
Process Groups
Process Improvement is Trickier Than You Think
Product Development
Product Market Fit
Product-Driven versus Customer-Driven by Ribbon Farm
Product-Led Growth
Product: Delivery Systems at Scale
Products
Profile: Agents as the Medium
Profile: LLMs as the Medium
Profit First
Program management vs. project management
Progress through Storytelling
Project Management
Project Management Unraveled
Project Management Unraveled Expanded
Project Portfolio Strategy
Prompt Engineering
Proof of Concepts
Protein Recipes
Prototype Theory
Psycho-Cybernetics
Psycho-Cybernetics Condensed Notes
Psychology of Intelligence Analysis
Pull Work That Pays
Python
Quantum Computing
Quarterly strategic planning using the fairytale structure
Question Based Selling
Question Tome
Questions That Sell
Questions to Analyze a Business
Queueing
QuillJs Events
Rackspace
Radical Focus
Raise or Fold
Random Notes
Rationality: From AI to Zombies
Ravi Gupta
Ray Dalio
Ray Dialo's Principles
Real Estate
Recon-ng
RecordRTC.js
Redfin
Redis
Reducing
Reforge
Reforge Artifacts
Reforge: Growth Series
Reforge: Retention and Engagement
Reframe Your Brain
Reframe Your Brain: The User Interface for Happiness and Success (The Scott Adams Success Series)
Reid Hoffman
Relationship and Rapport Mastery
Rendering
Repeatability
Resonate
REST best practices
Retrieval & Grounding
Reverse ETL
Reverse Proxy
Rice Recipes
Rich Hickey
Richard Feynman
Richard Odysseus
RichNwan Blog
Risk Savvy: How to Make Good Decisions
Roasted Potatoes Recipe
Rob Henderson
Robert Cialdini
Robert J. Sternberg and Practical Intelligence
Robert Moses
Robert Spolsky
Roberto Blake
Rohan Gilkes
ROIT: Return on Invested Token
Roles and RAS
Root Cause Analysis
Rotoviz
RSDTyler
Rubrics
RUFF 2025
RUFF Fantasy League
RUFF Final 2023 Plan
Rule Reviews
Rulebook
Rules of the Game
Runaway Selection
Running Effective Prospecting Days in B2B SaaS (with Remote Best Practices)
Ruth Chang
Ryan Singer
Saas Metrics -- A Guide to Measuring and Improving what Matters
SaaS Terminal Value under Scrutiny
Safety & Policy
Safety, Security, Privacy, Governance, and Responsible AI
Sales
Sales Acceleration Formula
Sales Compensation in a Consumption Pricing World
Sales Engineering
Sales Happen When Buyers Fear Missing Out
Sales Pipeline
Sales Pitch: How to Craft a Story To Stand Out and Win
Sales Process
Salesforce
SalesLoft
Salt
Salt, Fat, Acid, Heat
Sam Altman
Sam Hinkie
Same as Ever: A Guide to What Never Changes
Sandler Method
SBA
Scaling People
Scaling The Things That Don't Scale
Schema and Statement Evolution with Confluent Cloud for Apache Flink
Schema Registry
Scientific Method
Scientist
Scientist: Books
Scott Alexander
Scott Barrett
Scott Bessent
Scrapinghub x Confluent Cloud
Search for knowledge/information to complete a specific task/goal
SECI Model of Knowledge Management
Second Machine Age
Secrets to Winning Office Politics
Security and Governance
Security, Identity, and Compliance
Seeing Like a State
Segment
Segmentation
Segmentation In-Depth
Segmentation, Vectorization & Index Maintenance
Seibel on Startups
Select2
Self-Directed Behavior
Selfish Writing by Morgan Housel
Selling Confluent Cloud
Selling To Big Companies
Selling to Carol: Why targeting an ICP brings 10x more customers than you expected
Sense of Urgency
Sensemaking Sales
Server Architecture
Server-Fetched Partials
Serverless
Servers
Seven and a Half Lessons About the Brain
Sex
Sexual Tension
Shaan Puri
Shadow Work
Shape up
Shifting Left: Rearchitecting the Operational–Analytical Divide to Produce Trustworthy, AI-Ready Data atthe Source
Shyam Sankar
Sign-up Onboarding Flows
Signals for a successful venture
Simon Sinek
Simple Heuristics That Make Us Smart
Simple Made Easy
Simply Complexity
Sizing, Performance and Cost
Skill
Skill Prioritization and Ideas
Skin in The Game
SKOOL Games
Slack
Slava Akhmechet
Smallpox
Smart Brevity
Smash Bros
Snapshot Queries in Confluent Cloud for Apache Flink
Snowflake
So You Think You Can Prompt
Soft Skills for Managers
Software
Software Architecture
Software Engineer's Guide to Freelance Consulting
Software Tool Solutions
Solving Present Bias: Governance across time.
Somatotypes
Soros' Fallibility and Reflexivity
Source & Ingestion
Sources of Power
Sovereign Individual
SparkToro
Speak in Stories
Speak Like Churchill, Stand Like Lincoln
Speak with Impact
Spellbinding Sentences
Spin Selling
Sports
Spy Secrets That Can Save Your Life
Standard Operating Procedures
Stanley Druckenmiller
Star Wars
Start From Demand
Start Small, Stay Small
Startup Analytics/Marketing Stack
Startup Ideas
State & Memory
Statement CFU Metrics in Confluent Cloud for Apache Flink
StaySassy Blog
Steal the Show
Steph Smith
Stephen Covey
Steve Jobs
Steven Pressfield
Stocks
Store Knowledge to later complete a specific task/goal
Stratechery
Strategies of Genius
Strategy to Manage User's Timezones across application
Strategy v. Execution
Stream Processing
Stream Processing
Stream Relational Processing Platforms
Streaming and Generative AI
Streaming Audio
Streaming Projects
Strengths Dictate Weaknesses
StrengthsFinder
Strong Phrases and Sayings
Studying Ads
Submitting a form that is using QuillJs
Subscribe Highlights from the Claude 4 system prompt
Sudden death in FIFA athletes
Suhail Doshi
Superapps
SuperPrompts
Surrounded by Idiots
Sweller's Cognitive Load Theory
Synthesis
System Fonts
System Thinking Tools
Systems Bible
Systems Institute Courses
Systems Thinking
TableFlow Technical Study Guide - First Principles
Tables and Topics in Confluent Cloud for Apache Flink
Tacit vs. Explicit Knowledge
Taiichi Ohno
Tailwind
Take Back Your Power
Talk Tracks and Definitions
Taylor Pearson's notes
TCAD Internal Prep Strategy
Teaching instead of Selling
Team of Teams
Technical Advisor Guidelines
Technical Advisor Guidelines: Data Engineering T-Shirt Sizing
Techniques of Visual Persuasion
Technology
Ted Talks: The Official Guide to Public Speaking
Teleology
Template for Critical Analysis of Arguments
Tempo
Temporal Landmarks
Temporal.io
Tenancy, Quotas & Metering
Text-Heavy Design Presentation
TGSiTW: Condensed
TGSiTW: Condensed JBP
TGSiTW: Condensed Simple
TGSiTW: Condensed Simple II
That's Interesting
That's Not What I Meant
The 1-Page Marketing Plan
The 5 Part Process to Networking Effectively
The 6 AI Engineering Patterns
The 7 Primal Pains
The A-Z guide to Startup Metrics
The Agent Development Life Cycle
The architecture of today's LLM applications (2023)
The Art of Action
The Art of Extreme Commit
The Art of Impossible
The Art of Profitability
The Art of Strategy
The Art of Strategy
The Art of Strategy: A Game Theorist's Guide to Success in Business and Life
The Art of Winning
The As If Principle
The Bedrock Family
The Beginning of Infinity
The Beginning of Infinity w/ Claude
The Bid/Ask Spread of Top Talent
The Book of Elon: A Guide to Purpose and Success
The Book of Why
The Challenger Sale
The Checklist Manifesto
The Chimp Paradox
The cognitive categories of knowledge work
The Complete Guide to Command of the Message, Challenger & Great Demo!
The Complete Guide to Planning Your Day
The Complete Guide to Sales Force Incentive Compensation
The Compound Effect
The Comprehensive Architecture of Shaping: Technical and Tactical Methods
The concept of a Unit
The Constant Questions
The Constitution of Knowledge
The Conversation Code
The Creative Act
The Creative Act: Organized Notes
The Creed of Future Me
The Crumbling Workflow Moat: Aggregation Theory's Final Chapter
The current state of AEO
The Dark Triad Playbook
The Deep Dive Into the Needs of Indiehackers and Micropreneurs
The Design of Everyday Things
The Design of Future Things w/ Claude
The Dictator's Handbook
The Economic Rationalist
The Effective Executive
The Elements of Execution
The Embedded Entrepreneur
The Existential Questions to Answer For Any Endeavour
The Extended Mind
The Factory and The Lab Thesis
The Fear That Steals Your Life's Work
The Fifth Discipline
The Five Dysfunctions of a Team
The Five Patterns That Separate Elite Revenue Teams From the Rest
The Flinch
The Folly of Fools
The Folly of Fools Insights
The Founder's Mentality
The Four Horsemen of the Tech Recession
The Four Steps to the Epiphany
The Full Stack Startup
The Gifts of Imperfection
The Gifts of Imperfection Notes
The Glyvian Think Tank
The Go-Giver
The Go-Giver
The Goal
The Golden Workflow
The Great CEO Within
The Greatest Businessman in The World
The Greatest Salesman in The World
The Growth Marketer's Playbook
The Habituation of Excellence
The Hawthorne Effect
The Heart of Innovation: A Field Guide for Navigating to Authentic Demand
The Information
The Inner Game of Tennis
The Inner Game of Work
The Intangible Skills You Can't Interview For
The Internet
The Joy of Game Theory
The Key Skill We Rarely Learn: How to Feel Your Feelings
The Knowing-Doing Gap
The Knowledge Economy Is Over. Welcome to the Allocation Economy
The Ladder of Success
The Language of Uncertainty
The Last Bus Problem
The Laws of Human Nature
The Lifecycle of a Webpage
The Logic of Failure
The Lost Art of Listening
The Magic of Big Thinking
The Math Behind Deep Learning
The Meaning of Ownership
The Meta on Learning 2026
The Midwit Trap: Why Sophistication Defeats Itself
The Mindfulness Game
The Molecule of More
The Mom Test
The Moral Case for Making Money
The MoSCoW Method
The most profitable skill of the 21st century (not AI)
The Nature of Value
The Neuroscience of Risk Taking
The One Question That Accelerates Every Ambitious Goal
The One Thing
THE OPERATOR'S MANUAL ON WINNING ANY RELATIONSHIP
The Order of Time
The Outsiders
The P Rules
The Pain is the Rehab
The Pain We Can't Outrun
The Philosophy of Progressive Refinement
The Phoenix Project
The Pipeline Delusion
The Potential of Integrating Intelligence and Intuition
The Power of Mathematical Thinking
The Power of Myth
The practical application of "Rocks, Pebbles, Sand"
The Presentation Architecture Framework
The Price We Must Pay
The Principles of Product Development Flow
The Principles of Scientific Management
The Process
The Prompt Report: A Systematic Survey of Prompting Techniques
The Psychological Needs of the Extremely Ambitious
The Psychology of Action: Linking Cognition and Motivation to Behavior
The Psychology of Strategy
The Psychology of Time Perception
The Psychology on How We Interact With Things
The Psychopathology of Beliefs
The Purpose-Built AI Services
The Pursuit of Wow!
The Qualified Sales Leader
The Ratchet Effect
The Rebel Allocator
The Reframing Matrix
The relationship between Mindfulness and Executive Functioning
The Remarkable Effect
The Revenue Takes Care of Itself
The RevOps Playbook: Owner.com CRO’s Secrets to Scaling
The Righteous Mind: Why Good People Are Divided by Politics and Religion
The rise of product-led sales
The Roomates
The SAAS Sales Method: Sales as a Science
The SageMaker Family
The Sales Development Playbook
The Science of Storytelling
The scientific literature of visualization
The Score Takes Care of Itself
The Scout Mindset
The Self as a Center of Narrative Gravity
The Short Case for Nvidia Stock
The Six Fundamental Principles of Design
The Spiderweb Strategy
The Star Principle
The Status Game
The Status Game Insights
The Stepping Stones You're Not Building
The Strange Order of Things
The Strategy-Focused Organization
The Subtle Art of Not Giving a Fuck
The Talent Factory: How To Identify and Develop A-Players for Your Team
The Ten Principles Behind Great Customer Experiences
The Three Greatest Survey Questions Ever
The Toyota Way
The Transaction Costs of Work
THE TROUBLE WITH INCENTIVES: THEY WORK
The Trusted Advisor
The Trusted Advisor Revised Notes (Claude)
The Ultimate Hack: How To Meet Anyone and Make Success Inevitable
The Ultimate Sales Machine
The Undercurrent Skills Maturity Matrix
The unprofitable SaaS business model trap
The Upside of Your Dark Side: Why Being Your Whole Self--Not Just Your "Good" Self--Drives Success and Fulfillment
The value of an activity log
The Virus of The Mind
The War of Art
The Way of The Superior Man
The Way Richard Works
The White Pill Manifesto
theHarvester
Theory of Constraints
Theory of Mind
Things 10X People Do
Things I know at 40 I wish I knew at 20 by Mark Manson
Things That Make Us Smart
Things That Make Us Smart w/ Claude
Think Twice
Thinking and Deciding
Thinking Fast and Slow
Thought Experiments
Three ways to get the most from your Confluence personal space
Through the Language Glass
Tiago Forte
Tim Denning
Tim Ferris
Tim O'Reilly
Time and Watermarks in Confluent Cloud for Apache Flink
Time Asymmetry
Time Management
Time Management Advice for Sales Reps
Tinkertoys
To learn anything well
Tony Robbins
Tool Use v. RAG
Tools
Top 10 Connectors Cheatsheet
Top 7 Leadership Styles
Top Interpersonal Speaking Skills
Top Two Reasons for Churn
Topics to discuss with Life Coach
Traits I Value
Transactional Analysis Frames
Transferring Tacit Knowledge
Transparency is the Key to Accountability
Transport & Client
Triangulating Market Sentiment
Tricks for Introverts
Trigger List
Troubleshooting Issues in MySQL
Troubleshooting Laravel Environment Variables
Trueridge
Trueridge Dossiers
Trueridge: 10X Leverage
Trueridge: Accelerated Influence
Trueridge: Products
Turbolinks vs JS Framework
Turning Desire Into Dollars
Turning Pro
Tutorial Introduction to Streaming Application Development
Tutorials
Twilio
Twint
Twitter Hack of July 2020
Tycoons
Uber
UiPath
Ultimate Business Discovery Questions
Ultimate Organizational Skill List
Ultimate Question Vault
Ultimate Writing Handbook for Nonfiction
Umwelt
Uncapped Podcast
Understanding a forecast call
Understanding Composer
Understanding CORS
Understanding DNS
Understanding Docker-Compose
Understanding EC2
Understanding Environment Variables
Understanding Flexbox
Understanding How People Use The Web
Understanding HTTP
Understanding HTTP caching
Understanding Javascript Environment and Execution
Understanding Javascript Window Object and It's Descendants
Understanding Laravel Service Container
Understanding Laravel Spark
Understanding Media: The Extensions of Man
Understanding Michael Porter
Understanding Michael Porter: The Essential Guide to Competition and Strategy
Understanding Motivations > Marketing Hacks
Understanding MySQL Indexes
Understanding MySQL Statements
Understanding Networking Ports
Understanding OAuth2
Understanding PHP
Understanding Social Network
Understanding Try, Catch, Finally statements in PHP
Unearned Wisdom Notes
Unit
Unit Economics
Unit of Work
Unit of Work with ChatGPT
University of Chicago Writers Lab
Unreasonable Success and How to Achieve It
Use Case Based Sales Pipeline
Useful Array Functions in PHP
Useful Complexity of Thought Management Tools
Useful HTTP Codes
Useful php date tokens
Useful PHP file functions
Useful PHP Unit commands
Useful Prompts
Useful Terms
User Stories
User-defined Functions in Confluent Cloud for Apache Flink
Using Autonomous Agents with Nat Elliason
Using Faker PHP package
Using Jigsaw from Tighten
Using Laravel Eloquent Effectively
Using Nano in terminal
Using Tailwind UI
Using the ideas of Donald Reinersten to Intuit the Triangle (More, Faster, Better)
Using The Trust Equation
Valuation Mastery
Value Add Disease
Value Investing
Values in Therapy
Vaughn Tan
Venkatesh Rao
Virology
Visions, Strategy, Tactics, Execution
Visualization Techniques for High Output
Vivid Visions
Voice of the Customer
Vox Day
Vue
Vyv App
Wanting
Warren Buffet
Way of The Wolf
Ways to present website information
Web 2.0 Framework
Web Analytics 2.0
Web Application Architecture
Website Information Architecture
Weekly/Monthly/Quarterly Knowledge Review
Wenger's communities of practice theory
Wes Kao
What Are AI SuperPrompts And Why They Are Important.
What are some techniques startups can use to initially draw attention to their product?
What are some things about India that foreigners don't believe until they come to India?
What are some top automation tools that entrepreneurs use, things used for organizing life, contacts, deals, finding information, networking, and getting things accomplished?
What are the best landing page builders?
What are ways in which poor children can outperform rich privileged children?
What can cause a great startup to fail?
What drives great entrepreneurs
What great AI Teams do
What is a CDN
What is a Mindset
What is the best detailed advice you'd give to a software or mobile app start-up company?
What is Value
What is your main advice for those starting their own business?
What makes a good rule
What qualities are shared by most successful startup home pages?
What should every aspiring writer know about writing?
What the CEO Wants You To Know
What Theory is Not, Theorizing Is
What to do when your client doesn't know they have a problem
What We Learned from a Year of Building with LLMs
What will be true about data 10 years from now
When trying to decipher information from influence
Which channels should B2B company use to reach potential customers and effectively promote its product?
Which Self
Why Acceptance Works In Behavior Change
Why Buddhism is True: The Science and Philosophy of Meditation and Enlightenment
Why Deadlines Matter
Why Ideas First Approach To Innovation is Misguided
Why Input Goals Matter w/ Claude
Why Search Before Generate
Why you would need your own servers vs Cloud Vendors
Wide Receiver Stats That Matter for Fantasy Football
Will To Power
William Beteet III
William Edwards Deming
Willpower Doesn't Work
Wired for Story
Wisdom by Intellectual Belief
Wisdom of Crowds
Wooden's Leadership Game Plan
Words
Work Principles 2026
Working Backwards
Working The System
Working with Evolo landing page
Working with Laravel Eloquent and dates
Working with Laravel HTTP Responses
Working with Math and Numbers in Javascript
Worst To First
Wrestle Your Devils
Write-Ahead Log
Writing & Communication Theories
Writing Handbook
YNAB
You Aren't Learning If You Don't Close the Loops
You Need More Volume
You Need to Ship Faster
You Should Write An Agent
Your Discomfort Tolerance is a Muscle—And It's Probably Atrophied
Your job is to solve problems, not look smart
Zachary Perret
Zain Kahn
Zero To One
Zinc
Microservices with Confluent
🧠 Free Recall
❓ Quiz
🔍 Cues
🔮 Simulation
🥷 ShadowBox
📐 Encode Cards
🎙️ Generate Documentary
The Data Dichotomy: Rethinking the Way We Treat Data and Services If you were to stumble upon the whole microservices thing, without any prior context, you’d be forgiven for thinking it a little strange. Taking an application and splitting it into fragments, separated by a network, inevitably means injecting the complex failure modes of a distributed system. Yet, whilst the approach certainly involves many independent services, the goal is a little broader than simply running them across different machines. It’s about facing up to a world that is, itself, inherently distributed. Not in some narrow technical sense, but rather as a broad ecosystem composed from many people, many teams and many programs, all of which need to do their own thing to some degree or other. Companies for example are collections of disparate systems which, collectively, further some common aim. For decades we have largely ignored this fact, patching them together with FTP file transfers or enterprise integration tools, whilst concentrating on our own somewhat isolationist goals. But services change this. They force our heads up above the parapet, into a world of collaborative, inter-dependent programs. But to be successful at this we must recognize, and design for, two fundamentally different worlds: The external world, where we live in an ecosystem of many other services, and our private, internal world, where we alone reign king. But this distributed world is a little different to the one we grew up in. The rules of traditional, monolithic software engineering no longer hold water in the same way. So getting such systems right is more than putting together a cool sketch on a whiteboard or a sexy proof of concept. It’s about making these things work successfully over time. Fortunately, services have been around for a long time, albeit in a few different guises. The lessons from SOA still hold, despite the recent sprinkling of Docker, Kubernetes and some slightly over-coiffed hipster beards. So today we’re going to look at how the rules have changed, why we should rethink our approach to services and the data they share, and why we need a different toolset to do this. O’Reilly Book: Designing Event Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Encapsulation Isn’t Always Your Friend Microservices are independently deployable. It is this attribute, more than any other, that give them their value. It allows them to scale. To grow. Not so much in the sense of scaling to quadrillions of users or petabytes of data (although they may help with that), but rather scaling in people terms, as your teams and organization grow. But independence itself is a double edged sword. It means a service can iterate quickly and freely. But if one service implements a feature which requires another service to change, we end up having to make changes to both services at around the same time. Whilst this is easy in a monolithic application, where you can simply make the change and do a release, it’s considerably more painful where independent services must synchronize. The coordination between teams and release cycles erodes agility. The typical approach is to simply avoid such pesky, crosscutting changes by cleanly separating responsibilities between services. A Single Sign-On service is a good example. It has a well-defined role which can be cleanly separated from the roles other services play. This clean separation means that, even in the face of rapid requirement churn in surrounding services, it’s unlikely the SSO service will need to change. It exists in a tightly bounded context. The problem is that, in the real world, business services can’t typically retain the same, clean, separation of concerns. For example, business services inevitably rely more heavily on one another’s data. If you’re an online retailer, the stream of Orders, the Product Catalog or Customer Information will likely find it’s way into the requirements of many of your services. Each of these services needing broad access to these datasets to do their work. Most business services share the same stream of facts. This makes their futures inevitably intertwined. So this is an important point to note, whilst services work well for infrastructure components, which have the luxury of living largely in isolation, the futures of most business services are far more tightly intertwined. The Data Dichotomy Service-based approaches may have been around for a while, but they still offer relatively little insight into how to share significant datasets between services. The underlying issue is that data and services don’t sing too sweetly together. On one side, encapsulation encourages us to hide data; decoupling services from one another so they can continue to change and grow. This is about planning for the future. But on the other side, we need the freedom to slice and dice shared data like any other dataset. This is about getting on with our job right now, with the same freedoms as any other data system. But data systems have little to do with encapsulation. In fact, quite the contrary. Databases do everything they can to expose the data they hold. They come with a wonderfully powerful, declarative interface that can contort data into pretty much any shape you might desire. Exactly what you need for exploratory investigation, but not so great for managing the onset of complexity in a burgeoning service estate. So we find ourselves faced with a conundrum. A contradiction. A dichotomy: Data systems are about exposing data. Services are about hiding it. These two forces are fundamental. They underlie much of what we do, subtly jostling for supremacy in the systems we build. As we evolve and grow service-based systems, we see the effects of this Data Dichotomy play out in a couple of different ways. Either a service interface will grow, exposing an increasing set of functions, to the point it starts to look like some form of kookie, homegrown database. Alternatively, frustration will kick in and we add some way of extracting and moving whole datasets, en masse, from service to service. Now creating something that looks like a kookie, shared database can lead to a set of issues of its own. We won’t go into the perils of shared databases here, but suffice to say they represent significant and expensive engineering and operational challenges for companies that attempt them. To make matters somewhat worse, data volume actually amplifies this service boundary problem. The more shared data is hidden inside a service boundary, the more complex the interface will likely become, and the harder it will be to join datasets across different services. Yet the alternative – extracting and moving whole datasets – has problems too. A common approach to this is to simply extract and hold the whole dataset and then store it in a local database inside each consuming service. The problem is that different services make different interpretations of the data they consume. They keep that data around. Data is altered and fixed locally. Pretty soon it doesn’t represent the source dataset much at all. The more mutable copies, the more data will diverge over time. Making matters worse, divergent data is very hard to fix in retrospect (MDM is really a sticky-plaster over this). In fact some of the most intractable technology problems that businesses encounter arise from divergent datasets proliferating from application to application. To address this we need to think about shared data in a slightly different way. We need to consider it a first class citizen of the architectures we build. Pat Helland calls this data-on-the-outside, and this is a useful distinction to make. We need encapsulation so we don’t expose a service’s internal state. But we need to make it easy for services to get access to shared data so they can get on and do their jobs. The problem is that none of the approaches available today, Service Interfaces, Messaging or a Shared Database, provide a good solution for dealing with data-on-the-outside. Service interfaces are poorly suited to sharing data at any level of scale. Messaging moves data, but provides no historical reference, and this leads to data corruption over time. Shared databases concentrate too much in one place, stifling progress. Hence we inevitably get stuck in a cycle of data Inadequacy: The Cycle of Data Inadequacy Streams: A Decentralized Approach to Data and Services So, ideally, we’d like to rejig the way services approach shared data. Now any such approach has to face up to the aforementioned dichotomy, there is no technical fairy dust we can sprinkle on liberally and magic it away. But we can reframe the problem, and pick a subtly different compromise. This particular compromise involves a degree of centralization. We can use a Distributed Log for this as it provides retentive, scalable streams. Now we need our services to be able to join and operate on these shared streams, but we want to avoid complicated, centralized ‘God Services’ that do this type of processing. So a better approach is to embed stream processing into each consuming service. That means services can join together various shared datasets and iterate on them at their own pace. One way to achieve this is to use a Streaming Platform. There are a number of options available, but here we’ll consider Kafka, as it’s use of Stateful Stream Processing makes it particularly well suited to this problem. Using a distributed log pushes us down a fairly well-trodden path, one which uses messaging to make services Event Driven. Such approaches are generally considered to provide better scalability and better decoupling than their Request-Response brethren, as they move flow control from the sender to the receiver. This increases the autonomy of each service. In fairness it comes at a cost: you need a broker. But for significant systems, this is often a tradeoff worth making (less so for your average web app) Now if the broker is a distributed log, rather than a traditional messaging system, a few additional properties can be leveraged. The transport can be scaled out linearly in much the same way as a distributed file system. Data can also be retained in the log, long term. So it’s messaging, but it’s also storage. Storage that scales, and without the perils of shared, mutable state. Then a stateful stream processing engine can be used to embed the declarative tools of a database right inside the consuming services. This point is important. Whilst data is stored in shared streams, which all services might access, the joins and processing a service does, is private. The smarts are isolated inside each bounded context. Address the data dichotomy by sharing an immutable stream of state. Then push the function into each service with a Stateful Stream Processing Engine. So if your service needs to operate on the company’s Orders, Product Catalogue or Inventory, it has full access: you decide which datasets should be combined, you decide where it executes and you decide when and how to evolve it over time. This means that, whilst data itself is shared, operation on that shared data is fully decentralized. It sits entirely inside each service boundary, in a world where you alone reign king. Share data in a way that keeps it true to source. Encapsulate function, not at source, but in each service that needs it. Now it turns out that sometimes data must be moved, en masse. Sometimes a service needs a local, historic dataset in a database engine of their choice. The trick here is to ensure that the copy can be regenerated from source at will, by going back to the Distributed Log. Connectors in Kafka help with this. So there are a few specific advantages to this approach: Data is shared as streams, which can reside in the log long term, but the mechanism for manipulating that shared data is embedded into each bounded context, making it easy to iterate quickly and freely. This balances the data dichotomy. Datasets can easily be joined across different services on the fly. This makes it easier to interact with shared data, avoiding the need to maintain local datasets in a database. Stateful Stream Processing only caches data, the “golden source” is the shared log, so the problems of data diverging over time are far less prevalent. Services are event driven in essence, this means that, as datasets grow, services can still react quickly to business events. Scalability concerns move from services to the broker. This makes it much easier to build simpler services that don’t need to worry about scale. Adding new services doesn’t require upstream services to change. This makes it easier to plug new services in. So this is more than just REST. It’s a toolset that embraces shared data, in a decentralized way. This post is really just a taster, glossing over many details. We still need to dig into how we balance request-response and event-driven paradigms. We’ll do this in the next post. There are subjects we need to better understand too, like why Stateful Stream Processing is so valuable. The third post will look at this. There are also some other powerful constructs we can take advantage of, if we choose this path, such as Exactly Once Processing. This is a game changer for distributed, business systems as it provides the transactional guarantees of XA in a scalable form. The fourth post will go into that. Finally, we need to run through the nitty gritty of implementing these things. But for now, just remember this: The Data Dichotomy describes a tension we must all face when we build business services. We should be mindful of this fact. The trick is to switch things up: To consider shared data a first-class citizen. Something we design for. Stateful Stream Processing provides a unique compromise for this. It avoids the central “God Components” that typically stifle progress. But moreover, it brings the immediacy, scalability and fault tolerance of streaming data pipelines and embeds them right inside each service. So we focus on a holistic stream of consciousness which any service can dip into to make its decisions. This makes services more scalable, fungible and autonomous. So not only will they look good on whiteboards and in POCs, they’ll go on to survive and evolve for decades. Build Services on a Backbone of Events For many, microservices are built on a protocol of requests and responses. REST etc. This approach is very natural. It is after all the way we write programs: we make calls to other code modules, await a response and continue. It also fits closely with a lot of use cases we see each day: front facing websites where users hit buttons and expect things to happen. But when we step into a world of many independent services, things start to change. As the number of services grows gradually over time, the web of synchronous interactions grows with them. Previously benign availability issues start to trigger far more widespread outages. Our unfortunate Ops Engineers end up donning deerstalkers, as they play out distributed murder mysteries. Frantically running from service to service, piecing together snippets of second hand information (who said what, to whom, and when?). This is a well known problem and there are a number of solutions. One is to ensure your individual services have significantly higher SLAs than your system. Google provide a protocol for doing this. The alternative is to simply break down the synchronous ties which bind services together. We can use asynchronicity as a mechanism for doing this. If you’re working in online retail you’ll find synchronous interfaces like getImage() or processOrder() feel natural. Calls which expect an immediate response. But when a user clicks ‘Buy’ they trigger a complex and asynchronous process. A process that takes a purchase and physically delivers it to the user’s door, way beyond the context of the original button-click. So splitting software into asynchronous flows allows us to compartmentalise the different problems we need to solve, as well as allowing us to embrace a world that is itself, inherently asynchronous. In practice we tend to embrace this automatically. We’ve all found ourselves polling database tables for changes, or implementing some kind of scheduled cron job to churn through updates. These are simple ways to break the ties of synchronicity, but they always feel like a bit of a hack. There is a good reason for this. They probably are. So we can condense all these issues into a single observation. The imperative programming model, where we command services to do our bidding, isn’t a great fit for estates where services are operated independently. In this post we’re going to look at the other side of the architecture coin: composing services not through chains of commands, but rather through streams of events. This is a valid approach in its own right. It also forms a baseline for the more advanced patterns we’ll be discussing later in this series, where we blend the ideas of event-driven processing with those seen in Streaming Platforms. Commands, Events and Queries Before we dive into an example, we need to fix three simple concepts. There are three ways that services can interact with one another: Commands, Events and Queries. If you’ve not considered the distinction between these three before, it’s well worth doing so. The beauty of events is they are both fact and trigger. Data-on-the-outside that can be reused by any service in the system. But from a services perspective Events lead to less coupling than Commands and Queries. This fact is important. The three mechanisms through which services interact: Commands are an action. A request for some operation to be performed in another service. Something that will change the state of the system. Commands expect a response. Events are both a Fact and a Trigger. Something that has happened, expressed as a notification. Queries are a request to look something up. Importantly, queries are side effect free; they leave the state of the system unchanged. A Simple Event Driven Flow Let’s start with a simple example: a customer ordering a widget. Two things happen next: The associated payment is processed. The system checks to see if more widgets need to be ordered. In the request-driven approach this can be represented as a chain of commands. There are currently no queries. The interaction would look like this: The first thing to note is the business process for “Buying more Stock” is initiated by (called by) the Orders Service. This mingles responsibilities across the two services. Ideally we’d have a better separation of concerns. Now if we can represent the same flow using an Event Driven approach things work a little better. The UI Service raises OrderRequested and awaits OrderConfirmed (or Rejected) before returning to the user. Both the Orders Service and the Stock Service react to the raised event. Looking at this closely, the Interaction between the UI service and the Orders Service hasn’t changed all that much, other than they communicate via events, rather than calling one another directly. The Stock service is interesting though. No longer does the Orders Service tell it what to do. It controls whether it partakes in the interaction, or not. This is a very important property of this type of architecture, termed Receiver Driven Routing. Logic is pushed to the receiver of the events, rather than the sender. The burden of responsibility is flipped! Flipping control to receivers reduces the coupling between services, which opens up an important level of pluggability to the architecture. Components can be swapped in and out with ease. This element of pluggability becomes increasingly important as architectures become more complex. Say we were to add a service that manages pricing in real time, tweaking a product’s price based on supply and demand. In a command-driven world we would need to introduce a maybeUpdatePrice() method which is called by both the Stock Service and the Orders Service. But in the event-driven world re-pricing is just a service that subscribes to the shared stream, sending out price updates when relevant criteria are met. O’Reilly Book: Designing Event Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Blending Events and Queries The above example considered only commands/events. There were no queries (remember we defined all interactions as being one of commands, events and queries earlier). Queries are a necessity for all but the simplest architecture. So let’s extend the example a bit, making the Orders Service check that there is sufficient Stock before it processes a Payment. The request-driven approach to this would involve sending a query to the Stock service to retrieve the current stock count. This leads to a hybrid model, where the event stream is used purely for notification, allowing any service to dip into the flow, but queries go directly to source. For larger ecosystems, where services need to evolve independently, remote queries add a lot of coupling, tying services together at runtime. We can avoid such cross-context queries by internalising them. The stream of events is used to cache datasets in each service, where they can be queried locally. So to add this stock check, the Orders Service would subscribe to the stream of Stock events, storing them locally in a database. It would then query this ‘view’ to validate there is sufficient stock. Pure event-driven systems have no concept of remote queries – events propagate state to services where it is queried locally There are three advantages of this ‘Query by Event-Carried State Transfer’ approach: Better decoupling: Queries are local. They involve no cross-context calls. This ties services far less tightly than their request-driven brethren. Better autonomy: The Orders service has a private copy of the Stock dataset so it can do whatever it likes with it, rather than being limited to the query functionality offered by the Stock Service. Efficient Joins: If we were to “look up the stock” on every order, we would effectively be doing a join over the network between the two services. As workloads grow, or more sources need to be combined, this can be increasingly arduous. ‘Query by Event Carried State Transfer’ solves this issue by bringing queries (and joins) local. This approach isn’t without its downsides though. Services become inherently stateful. They need to keep track of, and curate, the propagated data set over time. The duplication of state can also make some problems harder to reason about (how do we decrement the stock count atomically?) and we should be careful of divergence over time. But all these issues have workable solutions, they just need a little consideration. This is well worth the effort for larger, more complex estates. The Single Writer Principle A useful principle to apply with this style of system is to assign responsibility for propagating events of a specific type, to a single service: a single writer. So the Stock Service would own how the ‘Inventory of Stock’ progresses forward over time, the Orders Service would own Orders, etc. This helps funnel consistency, validation and other ‘write path’ concerns through a single code path (although not necessarily a single process). So in the example below, notice that the Order Service takes control of every state change made to an Order, but the whole event flow spans Orders, Payments and Shipments, each managed by their respective services. Assigning responsibility for event propagation is important because these aren’t just ephemeral events, or transient chit-chat. They represent shared facts, the data-on-the-outside. As such, services need to take responsibility for curating these shared datasets over time: fixing errors, handling situations where schemas change etc. Here each color represents a topic, in Kafka, for Orders, Shipments and Payments. The basket service starts the ball rolling. When a user clicks ‘buy’ it raises Order Requested, waiting for the Order Confirmed event before it responds back to the user. The three other services handle the state transitions that pertain to their section of the workflow. So for example, after the payment is processed, the Order service pushes the Order from Validated to Confirmed. Blending Patterns and Clustering Services For some the pattern described above will look like Enterprise Messaging, but it is subtly different. Enterprise Messaging, in practice, focuses on state transfer, effectively tying databases together across a network. Event Collaboration is about services progressing some business goal, through a cascade of events, which trigger services into action. So it’s a pattern for business processing, rather than simply a mechanism for moving state. But we typically want to leverage both “faces” of this pattern in the systems we build. In fact, one of the beauties of this pattern is it can handle both the micro and the macro, or be hybridized where it makes sense. Combining patterns is also quite common. We might want the flexibility of remote queries rather than the overhead of maintaining a dataset locally, particularly as datasets grow. This makes it easier to deploy simple functions (which is important if we want to compose lightweight, serverless-styled, event flows), or because we’re in a stateless container or browser. The trick is to limit the scope of these query interfaces, ideally to within a bounded context*. It’s typically better to have an architecture with many specific, targeted views rather than a single, shared datastore. (*A bounded context, here, is a set of services which share the same deployment cycle or domain model.) To limit the scope of remote queries we can use a clustered context pattern. Here event flows are the sole communication pattern between contexts. But services within a context leverage both event-driven processing and request-driven views, where they are needed. In the example below we have three divisions, which communicate with one another only through events. Inside each one we use finer grained event-driven flows. Some of these include a view tier (query layer). This balances coupling with convenience, allowing us to blend fine-grained services with the larger entities; the legacy applications or off the shelf products, which inhabit many real world service estates. Clustered Context Model The five key advantages of event-driven services: Decoupling: Break long chains of blocking commands. Decompose synchronous workflows. Brokers decouple services so it’s easier to plug new ones in, or change those that are there. Offline/Async flows: When the user clicks a button many things happen. Some synchronously, some asynchronously. Design for the latter and the former falls out for free. State Transfer: event become the dataset of your system. Streams provide an efficient mechanism for distributing datasets, so they can be reconstituted, and queried, inside a bounded context. Joins: It’s easier to combine/join/augment datasets from different services. Joins are fast and local. Traceability: It’s easier to debug the distributed ‘murder mystery’ when there’s a central, immutable, retentive narrative journaling each interaction as it unfolds in time. Summing Up So in the event driven approach we use Events instead of Commands. Events trigger processing. They are also turned into views we can query locally. We revert back to remote, synchronous queries where necessary, particularly in smaller ecosystems, but we limit their scope in larger ones (ideally to a single bounded context). But all these approaches are just patterns. Guiding principles for sewing systems together. We shouldn’t be too dogmatic about them. For example a global query service can still be a good idea, if it is something that rarely changes (like a Single Sign On service). The trick is to start with a baseline of events. Events provide far less opportunity for services to couple themselves to one another, and flipping flow-control to the receiver makes for better separated concerns and better pluggability. The other interesting thing about event-driven approaches is they work as well for large, complex architectures as they do for small, highly collaborative ones. A backbone of events gives services the autonomy they need to evolve freely. Missing the complex ties of commands and queries. As for the Ops engineers, they’ll still be wearing deerstalkers and playing murder mystery, but hopefully not quite as often, and at least now the story comes with a script! But with all this talk of events, we’ve talked little of distributed logs or stream processing. When we apply this pattern with Kafka the rules of the system change. Retention in the broker becomes a tool we can design for, allowing us to embrace data-on-the-outside. Something services can refer back to. Streaming Platforms sit very naturally with this model of processing events and building views. Views embedded directly inside a service, views a service queries remotely, or views materialised as an on-going stream. This leads to a whole host of optimisations: leveraging the duality between event stream and event store, blending in stream processing tools that sift through the narrative, joining streams from many services and materializing views we can query. These patterns are empowering. They allow us to reimagine business processing using a toolset specifically designed for handling streams of events. But all these optimisations build on the concepts discussed here, simply applied with a more contemporary toolset. In the next post, we will look at how we can use the various features Kafka offers to build scalable, highly-available event driven services. Using Apache Kafka as a Scalable, Event-Driven Backbone for Service Architectures The last post in this microservices series looked at building systems on a backbone of events, where events become both a trigger as well as a mechanism for distributing state. These have a long history of implementation using a wide range of messaging technologies. But while Apache Kafka® is a messaging system of sorts, it’s quite different from typical brokers. It comes with both pros and cons and, like any technology we choose, this shapes the design of the systems we write. Originally built to distribute the datasets created by large social networks, Kafka wasn’t designed to implement JMS or AMQP; it was designed to scale. It remains the fastest messaging system even today. Of its original authors, Jay and Neha were versed in distributed systems, with Jay cutting his teeth on the open source NoSQL store Voldemort. Jun was a long-time database researcher at IBM. None of the team had backgrounds in messaging systems, but they weren’t attacking the “messaging problem” directly—at least not in the traditional sense. So Kafka was shaped by a different set of forces. A problem-space rethink, optimizing for scale and fault tolerance. Like many good outcomes in computer science, scale comes from simplicity. From workloads that flow with the grain of the underlying hardware. Kafka’s broker is, in many ways, the epitome of this. Distributed at its core, leveraging sequential workloads, and implemented with the rigor of a DBMS. This change of tack is important. When building service-based systems we need to think about the synchronous and the asynchronous. We need access to datasets that are owned elsewhere. We need to react responsively. We need to iterate quickly and independently to get things done. Kafka’s focus on scale, throughput and retention, along with the decoupling effects of a broker, make it an ideal message backbone for service estates that need all of these things. So this post is a more practical guide to building event driven systems using this unique type of broker. We’re going to examine how it differs from more traditional technologies, the key properties we should be leveraging in our system designs, configurations we should take note of and patterns we should implement or avoid. In later posts we’ll extend this further, pulling in the other elements of the wider Streaming Platform. Leverage the Log: An Efficient Structure for Retaining and Distributing Messages At the heart of Apache Kafka sits a distributed log. The log-structured approach is itself a simple idea: a collection of messages, appended sequentially to a file. When a service wants to read messages from Kafka it ‘seeks’ to the position of the last message it read, then scans sequentially, reading messages in order, while periodically recording its new position in the log. Taking a log-structured approach has an interesting side effect. Both reads and writes are sequential operations. This makes them sympathetic to the underlying media, leveraging pre-fetch, the various layers of caching and naturally batching operations together. This makes them efficient. In fact, when you read messages from Kafka, the server doesn’t even import them into the JVM. Data is copied directly from the disk buffer to the network buffer. An opportunity afforded by the simplicity of both the contract and the underlying data structure. So batched, sequential operations help with overall performance. They also make the system well suited to storing messages longer term. Most traditional message brokers are built using index structures, hash tables or B-trees, used to manage acknowledgements, filter message headers, and remove messages when they have been read. But the downside is that these indexes must be maintained. This comes at a cost. They must be kept in memory to get good performance, limiting retention significantly. But the log is O(1) when either reading or writing messages to a partition, so whether the data is on disk or cached in memory matters far less. From a services perspective there are a few implications to this log-structured approach. If a service has some form of outage and doesn’t read messages for a long time, the backlog won’t cause the infrastructure to slow significantly. A common problem with traditional brokers, which have a tendency to slow down as they get full. Being log-structured also makes Kafka well suited to performing the role of an Event Store, for those who like to apply Event Sourcing within their services. Rely on a Broker that Scales Out Linearly So the log is an efficient data structure for messaging workloads. But Kafka is really many logs. It’s a collection of files, filled with messages, spanning many different machines. Much of Kafka’s code involves tying these various logs together, routing messages from producers to consumers reliably, replicating for fault tolerance and handling failure. While running on a single machine is possible, production clusters typically start at three machines with larger clusters in the hundreds. When you read and write to a topic, you’ll typically be reading and writing to all of them, sharding your data over all the machines you have at your disposal. Scaling is thus a pretty simple affair. Add new machines and rebalance. The main advantage of this, from a microservice perspective, is that it takes the issue of scalability off the table. With Kafka, hitting a scalability wall is virtually impossible in the context of business services. This is empowering, especially when ecosystems grow. So Kafka-based services tend to pick patterns that are a little more footloose with bandwidth and data movement. Scalability opens other opportunities too. Single clusters can grow to company scales, without the risk off workloads overpowering the infrastructure. Topics with hundreds of terabytes are not uncommon, and with bandwidths carved up between the various services, using the multi-tenancy features makes clusters are easy to share. Segregate Load in Multi-Service Ecosystems Service architectures are by definition multi-tenant. A single cluster will be used by many different services. In fact it’s not uncommon for all services in a company to share a single cluster. But doing so opens up the potential for inadvertent denial of service attacks causing instability or downtime. To help with this, Kafka includes a throughput control feature which allows a defined amount of bandwidth to be allocated to specific services, ensuring that they operate within strictly enforced SLAs. Greedy services are aggressively throttled, so a single cluster can be shared by any number of services without the fear of unexpected network contention. This feature can be applied to either individual service instances or load balanced groups. O’Reilly Book: Designing Event Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Maintain Strong Ordering Guarantees While it often isn’t the case for analytics use cases, most business systems need strong ordering guarantees. Say a customer makes several updates to their Customer Information. The order in which these are processed is going to matter, or else the latest change might be overwritten with an older value. There are a couple of things we need to consider to ensure strong ordering guarantees. The first is that messages that require relative ordering need to be sent to the same partition. This is managed for you, you supply the same key for all messages that require a relative order. So our stream of Customer Information updates would use the CustomerId, as their sharding key, so that all messages for the same Customer would be routed to the same partition, and hence be strongly ordered. Sometimes key-based ordering isn’t enough, and global ordering is required. This often comes up when you’re migrating from legacy messaging systems where global ordering was an assumption of the original system’s design. To maintain global ordering use a single partition topic. Throughput will be limited to that of a single machine, but this is typically sufficient for most use cases of this type. The second use case we need to be careful of is retries. In almost all cases we want to enable retries in the producer, so that if there is some network glitch, long running GC, failure, etc, any messages that fail to be sent to the broker will be retried. The subtlety is that messages are sent in batches, so we should be careful to send these batches one at a time, per destination broker, so there is no potential for a reordering of events when failures happen and whole batches are retried. This is simply something we configure (max.inflight.requests.per.connection). Ensure Messages are Durable Kafka provides durability through replication. This means messages are written to a configurable number of machines so that, should one or more of those machines fail, the messages will not be lost. If you configure a replication factor of three, two machines can be lost without losing data. To make best use of replication, for sensitive datasets like those seen in service-based applications, configure three replicas per partition, set acknowledgements to “all” and min.insync.replicas to 2. This ensures that data is always written to at least two replicas, but Kafka will continue to accept messages should you lose a machine. Finally, as discussed earlier, configure retries in the producer. Highly sensitive use cases may require that data be flushed to disk synchronously. This can be done by setting log.flush.interval.messages = 1 — but this configuration should be used sparingly. It will have a significant impact on throughput, particularly in highly concurrent environments. If you do take this approach, increase the producer batch size, to increase the effectiveness of each disk flush on the broker (batches of messages are flushed together). Load Balance Services and make them Highly Available It’s common to run services in a highly available, load balanced configuration. So we might have two instances of the Orders service, reading messages from the Orders topic. In this case Kafka would assign half of the partitions to the first service instance, the other half to the second, so the load is spread over the two instances. Should one of the services fail, for whatever reason, Kafka will detect this failure and re-route messages from the failed service to the one that remains. If the failed service comes back online, load flips back again. This process actually works by assigning whole partitions to different consuming services. A strength of this approach is that a single partition can only ever be assigned to a single service instance (consumer). This is an invariant, implying that ordering is guaranteed, even as services fail and restart. So services inherit both high availability and load balancing, meaning they can scale out, handle unplanned outages or perform rolling restarts without service downtime. In fact Kafka releases are always backwards compatible with the previous version, so you are guaranteed to be able to release a new version without taking your system offline. Hold Key-Based Datasets in a Compacted Form By default, topics in Kafka are retention based: messages are retained for some configurable amount of time. Kafka also ships with a special type of topic that manages keyed datasets: that’s to say data that has a primary key (identifier) and you might put in, or get from, a database table. These ‘Compacted Topics’ retain only the most recent events, with any old events, for a certain key, being removed. They also support deletes. Compacted topics work a bit like simple LSM trees. The topic is scanned periodically and old messages are removed if they have been superseded (based on their key). It’s worth noting that this is an asynchronous process, so a compacted topic may contain some superseded messages, which are waiting to be compacted away. Compacted topics let us make a couple of optimisations. Firstly they reduce how quickly a dataset grows (as superseded events are removed), but we do this in a data-specific way, one that relates to the data itself, rather than say simply removing messages older than two weeks. Secondly, by making datasets smaller, it’s easier to move them from machine to machine. So say a service wishes to load the latest version of the Product Catalogue, and we have Products stored in a compacted topic in Kafka, the load can be performed quicker and more efficiently if it doesn’t have to load the whole ‘versioned history’ as well (as would be the case with a regular topic). This keeps load times down when we want to reprocess whole topics from scratch, load Event Sourced datasets, or load data into Kafka Streams. We’ll be looking at this a lot more closely later in the series as we introduce approaches for data automation and streaming services. Holding Data in Kafka Long-Term One of the bigger differences between Kafka and other messaging systems is that it can be used as a storage layer. There are a couple of patterns used for this. One is Event Sourcing, where we store each state change our service makes in a topic, which can be replayed in full. The second approach optimizes this to only keep the latest event, for each key, using a compacted topic (discussed above). The final approach is to hold both, linked via a Kafka Streams process. This gives you the best of both worlds. So imagine our service wants to expose JMX metrics that track a variety of operational measures, like the number of orders processed each day. We might keep the running totals for these in memory, then store these values periodically back to Kafka. When the service starts it would load these intermediary values back into memory. As we’ll see in the next post, we can use Kafka’s Transactions feature to do this in an accurate and repeatable way. For small datasets, stored in compacted topics, it’s useful to reduce the default segment size (log.segment.bytes) as the most recent segment of a compacted topic is never compacted. Finally, the Kafka Streams API actually provides richer semantics for data storage via its State Store feature. These layer RocksDB in front of a compacted Kafka topic and automate the flushing of data back to Kafka. We’ll cover these in more detail later in this series. Segregate Public and Private Topics When using Kafka for Event Sourcing or Stream Processing, in the same cluster through which different services communicate, we typically want to segregate private, internal topics from shared, business topics. Some teams prefer to do this by convention, but a stricter segregation can be applied using the authorization interface. Essentially you assign read/write permissions, for your internal topics, only to the services that own them. This can be implemented through simple runtime validation, or alternatively fully secured via TLS or SASL. Use Schemas to Manage the Evolution of Data in Time It’s typically preferable to use schemas to wrap the messages services exchange. Schemas provide a contract that defines what a message should look like. This is pretty intuitive. Importantly though, most schema technologies provide a mechanism for validating whether schemas are backwards compatible. So say you added a ‘returns code’ field to an Order schema. This would be a backwards compatible change, as old programs can still read the previous version of the schema as is, but new programs can also read the new ‘return code’ field. There are a fair few options available for this: Protobuf, JsonSchema etc but most projects in the Kafka space use Avro. Confluent provides the Confluent Schema Registry, which adds a level of runtime validation to Avro encoded messages, ensuring that incompatible messages will fail on publication, thus protecting the system from invalid data. If you use the Confluent Platform distribution, Kafka is bundled with the Schema Registry ready for you to use. How to Break Backwards Compatibility Most of the time, when we’re evolving and releasing our services, we maintain backwards compatibility between schemas. This keeps releases simple. Periodically however message formats will need to be changed in incompatible ways, most commonly when a whole schema needs to be reworked. Say we want to change the way an Order is modelled. Rather than being a single element, Cancels, Returns and Reorders are all to be modelled in the same schema. This kind of change would likely break backwards compatibility because the structure of the whole schema has to be changed. The most common approach for handling breaking changes like these is to create two topics: orders-v1 and orders-v2, for messages with the old and new schemas respectively. Assuming Orders are mastered by the Orders Service, this gives us a couple of options: The Orders service can dual-publish in both schemas at the same time. We can add a process that down-converts from orders-v2 topic to orders-v1 topic. A simple KStreams job is typically used to do this kind of down-conversion. Both approaches achieve the same goal: to give services a window in which they can upgrade, but the KStreams method has the advantage that it’s also easy to port any historic messages from the v1 to the v2 topics. Services continue in this dual-topic mode until all services have fully migrated to the v2 topic, at which point the v1 topic can be archived or deleted as appropriate. As an aside, in the last post we discussed applying the single writer principle. One of the reasons for doing this is it makes schema upgrades simpler. If we had three different services writing Orders it would be much harder to schedule a non-backward compatible upgrade without a conjoined release. Chain Services with Exactly-Once Guarantees One benefit of Kafka is it ships with an exactly once processing feature built in. This makes it very easy to chain together services, without the overhead of implementing idempotence manually. This particular feature is discussed in some detail in the next post. Prefer Events over Commands or Queries Kafka is used in a wide range of use cases, but like most technologies it has its sweet spot. It works very well for event-driven systems. However sometimes a request-response style is more appropriate, so what do you do? Say you need a way for the Orders service to query a stock count from the Stock Service. You can synthesise such a request over any asynchronous channel. This is typically done by creating two topics, a request topic and a response topic. Using Kafka for commands and queries is not uncommon, but it also isn’t its sweet spot. Kafka topics are more heavyweight than, say, HTTP. But you shouldn’t forget that, while HTTP is a lightweight protocol, real implementations need to worry about retries, circuit-breaking, rate-limiting, monitoring etc. (which is where service meshes often come in) but Kafka provides all these features out of the box. So it’s a tradeoff. If you choose to use commands or queries via Kafka use single partition topics and reduce the segment and retention sizes to keep the overhead low. Another alternative is to mix in a stateless protocol like HTTP, layered over a backbone of events. For example with a gateway. Alternatively you can open separate interfaces in each service, one for events and another for request response. This is a great pattern, and probably the most commonly used one we see. So Events are created for all state changes and HTTP is used for all request response. See figure below. This pattern makes a lot of sense because events are, after all, the dataset of your system (so Kafka works well). Queries are more like ephemeral chit-chat (so HTTP is perfect). We covered more event-driven design patterns in the last post. Building a Simple Event-Based System The most common pattern for smaller service implementations is to mix protocols: Kafka is used for moving data from service to service via event-carried state transfer, triggering services into action or being used as an event store. REST, or some other RPC protocol, is used for queries etc. But we should be careful with this pattern as our system grows. For larger, corporate-sized architectures, a pattern like the clustered context model (discussed in the last post) can be a better fit, as it promotes the use of event-carried state transfer while compartmentalizing remote queries. But for smaller service applications, the pattern described above is a simple and effective compromize. Summing Up Kafka is a little different from your average messaging technology. Being designed as a distributed, scalable infrastructure component makes it an ideal backbone through which services exchange and buffer events. There are obviously a number of unique elements to the technology itself but the ones that stand out are its ability to scale, its ability to run always on and its ability to retain datasets long term. We can use the patterns and features discussed in this post to build a wide variety of architectures, from fine-grained service-based systems right up to hulking corporate conglomerates. The approach is safe, pragmatic, and follows a tried and tested path. In the next post we’ll dig into the one major topic we couldn’t cover here: Kafka’s transactional guarantees. These are something of a game changer for service-based systems. The rest of this series will layer in the other elements of Streaming Platforms. We’ll bring in the Kafka Streams API. A rich, general-purpose interface for joining and processing datasets emitted from different services in real time. It takes a little extra effort, when compared with the simplicity of the pure event-driven approach discussed so far, but it is effort that is almost always worth making. Chain Services with Exactly-Once Guarantees This fourth post in the microservices series looks at how we can sew together complex chains of services, efficiently, and accurately, using Apache Kafka’s Exactly-Once guarantees. Duplicates, Duplicates Everywhere Any service-based architecture is itself a distributed system, a field renowned for being difficult, particularly when things go wrong. We have thought experiments like The Two Generals Problem and proofs like FLP which highlight that these systems are difficult to work with. In practice we make compromises. We rely on timeouts. If one service calls another service and gets an error, or no response at all, it retries that call in the knowledge that it will get there in the end. The problem is that retries can result in duplicate processing—which can cause very real problems. Taking a payment, twice, from someone’s account will lead to an incorrect balance. Adding duplicate tweets to a user’s feed will lead to a poor user experience. The list goes on. The UI makes a call to the Payment Service, which calls an external Payment Provider. The Payment Service fails before returning to the UI. As the UI did not get a response it eventually times out and retries the call. The user’s account could be debited twice. Paying for some product twice sounds pretty bad, particularly if you’re the one doing the paying, but many other scenarios seem far more innocuous. If a customer updates their address, and a failure causes the message to be sent twice, the end result would likely be the same. The address would be updated, to the same value, a second time. Put another way, duplicate messages have no affect on this particular outcome; the operation is naturally idempotent. We use this trick all the time when we build systems, often without even thinking about it. But what is really happening here is the “address updates” are being deduplicated at the end point, probably by storing them in a database by CustomerId. We can use the same trick to make payment messages idempotent. To ensure correctness the Payment Provider would have to de-duplicate payments, typically using a database, making use of the unique IDs of each payment. This need for deduplication applies equally to synchronous and asynchronous systems. So it is actually the use case itself that is important. So long as deduplication happens at the start and end of each use case, it doesn’t matter how many duplicate calls are made in between. This is an old idea, dating back to the early days of TCP. It’s called the End-to-End Principle. The rub is this: As the number of services increases, so does the cognitive overhead of considering how your program will be affected when duplicates occur. Also, as a variety of use cases build up over time, the end-point trick becomes less useful, as it becomes less clear where each one starts, and ends. As a result most event-driven systems end up deduplicating on every message received, before it is processed, and every message sent out has a carefully chosen ID, so it can be deduplicated downstream. This is at best a bit of a hassle. At worst it’s a breeding ground for errors. But if you think about it, this is no more an application layer concern than ordering of messages, arranging redelivery, or any of the other benefits that come with TCP. We choose TCP over UDP, because we want to program at a higher level of abstraction, where delivery, ordering, etc., are handled for us. So we’re left wondering why these issues of duplication have leaked up into the application layer? Isn’t this something our infrastructure should solve for us? Streaming Platforms, like Apache Kafka, address these issues, allowing you to build long chains of services, where the processing of each step in the chain is wrapped in exactly-once guarantees. O’Reilly Book: Designing Event Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Building Services with Exactly-Once Guarantees Apache Kafka ships with exactly-once processing built in. Now this isn’t some magic fairy dust that sprinkles exactly-onceness over your entire system. Your system will involve many different parts, some based on Kafka, some based on other technologies, the latter of which won’t be covered by the guarantee. But it does sprinkle exactly-onceness over the Kafka bits; the interactions between your services. This frees services from the need to deduplicate data coming in, and pick appropriate keys for data going out. So we can happily chain services together, inside an event driven workflow, without these additional concerns. This turns out to be quite empowering. As a simple example imagine we have an account validation service. It takes Deposits in, validates them, then sends a new message back to Kafka marking the deposit as validated. You may know that Kafka records the progress that each consumer makes by storing an offset in a special topic, called consumer_offsets. So to validate each deposit exactly once we need to perform the final two actions (a) send the “Deposit Validated” message back to Kafka and (b) commit the appropriate offset to the consumer_offsets topic—as a single atomic unit. The code for this would look something like the below: But unlike direct service calls, Kafka is a message broker. An intermediary. This means there are actually two opportunities for duplication. Sending a message to Kafka might fail before an acknowledgement is sent back, with a resulting retry and potential for a duplicate message. On the other side the read from Kafka might fail, before offsets are committed, meaning the same message might be read a second time, when the process restarts. So idempotence is added to the broker to address the send-side. Idempotence, in this context, is just deduplication. Each producer is given an identifier, and each message is given a sequence number. The combination of the two uniquely defines each batch of messages sent. The broker uses this unique sequence number to work out if a message is already in the log and discards it if it is. This is a significantly more efficient approach than storing every key you’ve ever seen in a database. That covers the send side, ensuring we don’t get duplicates in the log. On the read side we might simply deduplicate. But Kafka’s Transactions actually provide a broader guarantee, more akin to transactions in a database, tying all messages sent together in a single atomic unit. Be it to one topic or to many topics, it doesn’t matter. But whilst there are conceptual similarities, the mechanism is very different from that seen in your standard DBMS. It’s also a lot more scalable. So idempotence is built into the broker, meaning you can’t get a duplicate message in the log. Then a Transactions API is layered on top, to handle the read side, as well as chaining together subsequent calls atomically. How Kafka’s Transactions Work Under the Covers Looking at the code example above you’ll see that transactions like these, which are designed for Streaming Platforms, look a bit like transactions in a database. You start a transaction, write messages to Kafka, then commit or abort. But the implementation is quite different to that found in a database, so it’s worth building an understanding of how it actually works. One key difference is the use of marker messages which make their way through the various streams. Marker messages are an idea first introduced by Chandy & Lamport almost thirty years ago in a method called the Snapshot Marker Model. Kafka’s transactions are an adaptation of this idea, albeit with a subtly different goal. While this approach to transactional messaging is complex to implement, conceptually it’s quite easy to understand. Take our previous example above, where two messages were written to two different topics atomically. One message goes to the Deposits topic, the other to the committed_offsets topic. ‘Begin’ markers are sent down both*. We then send our messages. Finally, when we’re done, we flush each topic with a ‘Commit’ (or ‘Abort’) marker which concludes the transaction. Now the aim of a transaction is to ensure only ‘committed’ data is seen by downstream programs. To make this work, when a consumer sees a ‘Begin’ marker it starts buffering internally. Messages are held up until the “Commit” marker arrives. Then and only then are the messages presented to the consuming program. This buffering ensures that consumers only ever read committed data*. Conceptual model of transactions in Kafka To ensure each transaction is atomic the sending of the commit markers makes use of a Transaction Coordinator. There will be many of these spread throughout the cluster, so there is no single point of failure, but each transaction makes use of just one of them. The transaction coordinator is the ultimate arbiter that marks a transaction committed, atomically, and maintains a transaction log to back this up (this step implements two phase commit). For those that worry about performance, there is of course an overhead that comes with this feature, and if you were required to commit after every message the performance degradation would be noticeable. But in practice there is no need for that as the overhead is dispersed among whole batches of messages, allowing us to balance transactional overhead with worst-case latency. For example, batches that commit every 100ms, with a 1KB message size, have a 3% overhead when compared to in-order, at-least-once delivery. You can test this out yourself with the performance test scripts that ship with Kafka. In reality there are many subtle details to this implementation, particularly around recovering from failure, fencing zombie processes, and correctly allocating IDs, but what we have covered here is enough to provide a high-level understanding of how this feature works. You can find out more about the implementation here. *In practice a clever ‘optimisation’ is used to move buffering from the consumer to the broker, reducing memory pressure. Begin markers are also optimized out. Transactions that Span both Streams and State: A More Complex Example Not having to handle idempotence manually is certainly a boon, but transactions actually provide more rigorous guarantees. Kafka can be used both as a communication mechanism, pushing events from service to service, as well as a persistence mechanism, storing our service’s state so it can be reloaded after a restart. We can use transactions to tie these two concepts together, atomically. This turns out to be a powerful idea. Imagine we extend the previous example so our validation service keeps track of the balance as money is deposited. So if the balance is currently $50, and we deposit $5 more, then the balance should go to $55. We record that $5 was deposited, but we also store this ‘current balance’, $55, by writing it to a compacted topic. We wrap the ‘balance update’, along with the other two writes, in a transaction, ensuring the balance will always be atomically in-sync with deposits. Importantly, we can also read the current account balance when our service starts (or crashes and restarts) and know it is correct because it was committed transactionally back to Kafka. So the code would look something like this: What’s interesting about this example is we are blending concepts of messaging and state management. We listen to events, act, and create new events, but we also manage state, the “current balance”, in Kafka. All wrapped in the same transaction. When we discuss Stateful Stream Processing later in this series we’ll be leveraging this fact heavily through the more functional Kafka Streams API, which rolls in its own state management via state stores. Do we need Transactions? Can we do all this with Idempotence? People have been building event driven systems for decades, simply by making their processes idempotent with identifiers and databases. But implementing idempotence comes with some challenges. While defining the ID of an Order is relatively obvious, not all streams of events have such a clear concept of identity. If we had a stream of events representing “the average account balance per region per hour” we could come up with a suitable key, but you can imagine it would be a lot more brittle and error prone. Also, transactions encapsulate the concept of deduplication entirely inside your service. You don’t muddy the waters seen by other services downstream, with any duplicates you might create. This makes the contract of each service clean and encapsulated. Idempmotence, on the other hand, relies on every service that sits downstream correctly implementing deduplication, which clearly makes their contract more complex and error prone. What Can’t Transactions Do? There are a few limitations or potential misunderstandings of transactions that are worth noting. Firstly, they only work in situations where both the input and the output goes through Kafka. If you are calling an external service (e.g. via HTTP), updating a database, writing to stdout, or anything other than writing to and from the Kafka broker, transactional guarantees won’t apply and calls can be duplicated. So much like using a transactional database, transactions only work when you are using Kafka. Also akin to accessing a database, transactions commit when messages are sent, so once they are committed there is no way to roll them back, even if a subsequent transaction downstream fails. So if the UI sends a transactional message to the Orders Service and the Orders Service fails while sending messages of its own, any messages the Orders Service sent would be rolled back, but there is no way to roll back the transaction in the UI. If you need multi-service transactions consider implementing Sagas. Transactions commit atomically in the Broker (just like a transaction would commit in a database) but there are no guarantees regarding when an arbitrary consumer will read those messages. This may appear obvious, but it is sometimes a point of confusion. Say we send a message to the Orders topic and a message to the Payments topic, inside a transaction, there is no way to know when a consumer will read one or the other, or that they might read them together. But again note, this is identical to the contract offered by a transactional database. Finally, in the examples here we use the producer and consumer APIs to demonstrate how transactions work. But the Kafka’s Streams API actually requires no extra coding whatsoever. All you do is set a configuration and exactly-once processing is enabled automatically. But while there is full support for individual producers and consumers, the use of transactions with consumer groups is subtly nuanced (essentially you need to ensure that a separate transactional producer is used for each consumed partition) so we recommend using the Streams API when chaining together producers and consumers where consumer groups are supported without any extra considerations on your part. Making use of Transactions in your Services In the second post in this series we described, in some detail, a design pattern known as Event Collaboration. This is a good example of transactions simplifying a complex, multi-stage business workflow which is relatively fine grained. So while operations like contacting the Payment or Shipping Provider will still need careful, end-to-end IDs, the internal service interactions don’t inherit this concern. Later in the series we’ll discuss using the Streams API to build Streaming Services. Building reliable streaming services without transactions turns out to be pretty tough. There are a couple of reasons for this: (a) Streams applications make use of more intermediary topics and deduplicating them after each step is a burden (b) the DSL provides a range of one-to-many operations (e.g. flatMap()) which are hard to manage idempotently without the transactions API. Kafka’s transactions feature resolves these issues, along with atomically tying stream processing with the storing of intermediary state in state stores. Summing Up Transactions affect the way we build services in a number of specific ways: Firstly, they take idempotence right off the table for services interconnected with Kafka. So when we build services that follow the pattern: read, process, (save), send, we don’t need to worry about deduplicating inputs or constructing keys for outputs. Secondly, we no longer need to worry about ensuring there are appropriate unique keys on the messages we send. This typically applies less to topics containing business events, which often have good keys already. But it’s useful when managing derivative/intermediary data, say when we’re remapping events, creating aggregate events, or using the Streams API. Thirdly, where Kafka is used for persistence, we can wrap both messages we send to other services, and state we need internally, in a single transaction that will commit or fail. This makes it easier to build simple stateful apps & services. Finally, transactions give a kind of closure to services that read input and produce output. This encapsulates one of the key side effects of distributed programming, so you can build and test services in isolation without needing to worry about duplicate inputs. Put simply, when building event-based systems, Kafka’s transactions free you from the worries of failure and retries in a distributed world—a worry that really should be a concern of the infrastructure, not a concern of your code. This raises the level of abstraction, making it easier to get accurate, repeatable results from large estates of fine-grained services. Having said that, we should also be careful. Transactions remove just one of the issues that come with distributed systems, but there are many more. Coarse-grained services still have their place. But in a world where we want to be fast and nimble, Streaming Platforms raise the bar, allowing us to build finer grained “micro” services that behave as predictably in complex chains as they would standing alone. In the next post we’ll look at leveraging Kafka’s ability to retain messages long term, and how this changes the way we handle shared data. Data-on-the-outside. Messaging as the Single Source of Truth AUGUST 2, 2017 This post discusses Event Sourcing in the context of Apache Kafka®, examining the need for a single source of truth that spans entire service estates. Events are Truth One of the trickiest parts of building microservices is managing data. The reason is simple enough. In traditional systems there is a system of record, typically a database; the trusted resource for facts. In service-oriented systems there is no single source of truth, there are many. Different datasets are owned by a variety of different services. This is by design. Services will have a range of different roles: individual pieces in a far larger puzzle. Calling remote services works well in many simple cases, populating drop-down boxes or looking up products. But if you want to combine datasets from a whole range of services, for example, to build a complex report or populate a datagrid on a dashboard, the remote calls turn out to be far more painful: a hand-crafted ‘distributed join’ spanning several independent services. The bigger the ecosystem gets, the more data becomes fragmented, and the harder these challenges become. Events help with this. Events are typically sewn into an architecture because the concept of notification is desired. For example, if you’re building an online retail application, and you have a service that manages the shipping process, it makes sense to decouple that process from the user clicking ‘Buy’. This concept of notification, via a broker, breaks down complex call graphs, allowing processes to decouple themselves from one another. We discussed the benefits of event-driven design in this previous post. The kicker is that these events are much more than just notifications. Events are facts that evolve and tell a story. A narrative that describes your system’s evolution as a whole; a day in the life of your business, day after day, forever. So events solve two important architectural issues: (1) Managing real-world asynchronicity (2) Bringing together and manipulating datasets from different services. If we use a traditional messaging system to push events around, the events are ephemeral. They are sent, received and deleted without any form of ‘historic reference’. But what would happen if we kept these events around? The answer to this question is both interesting and formative: the events form a single source of truth, something most modern service estates are sorely lacking. Logs Never Lie and they Never Forget: Version Control for your Data. If we keep events in a log, it starts to behave like a version control system for our data. For example, if you were to deploy a faulty program, the system might become corrupted, but it would always be recoverable. The sequence of events provides an audit point, so that you can examine exactly what happened. Further, when the bug is fixed, you can rewind your service back, and start your processing again. This is useful for addressing questions like: why did this order mysteriously get rejected? Why is this balance suddenly in the red? These are impossible questions for systems that hold data as mutable state because the past is lost forever. Moreover, rewinding and replaying is typically far less error prone than crafting some kookie, one-off rollback script. Event Sourcing: Have One Source of Truth. Event sourcing is a simple but powerful idea. When we build ‘traditional’ event-driven systems, each service typically listens to events, records its actions in a database, and raises new events. For example, if a user buys a widget, the Orders service would save the order to a database, then raise an event so the next step in the process (validation) would be triggered. In this case, the database is the source of truth. That makes sense from the internal perspective of the Orders service. But if you think about it from the context of other microservices, the source of truth is really the event. This leads to a few different issues. The first is maintaining consistency. The events are dual-written to both the database and the event stream and both writes must be atomic. The second is that, in practice, it’s quite easy for the data in the database, and the events that are emitted by the service, to diverge. They end up with different data models, following different code paths. As events are fire and forget, the ‘forgetting’ part leaves you open to subtle bugs, which only come to light when some downstream team notices and starts shouting at you! Finally, unlike the event stream, the database is typically a mutable resource, so regeneration of past events is not an option. The alternative is to merge the two. We make the events first class entities which are used both internally within our service, as well as by other services downstream. So in an event sourced system, there is no dual-write. The events are the system of record. Services literally ‘eat their own dog food.’ Open, Evolutionary Architectures through Messaging that Remembers One view of good system design is that it can’t be found on a whiteboard, in a Visio document, or any other static resource. It comes from a system’s ability to evolve: to grow into new requirements, to leverage new hardware and development practices, to incrementally morph itself into the unknown. The event stream provides a foundation for this type of evolutionary approach. It is the data-plane that your services share. So as the events you exchange become a shared dataset in their own right, they blur the distinction between communication protocol and data store. The resulting historical reference is something new services can hook into. A powerful bootstrapping tool, opening the architecture to extension. For example, if you write a new service for fraud detection, that service requires orders, payments and customer information. This information is available in the log, either as a constantly updated stream or as a complete dataset: set offset = 0 and ingest. Using Apache Kafka as an Event Store As a technology, Kafka is well suited to the role of storing events long term. It provides powerful scalability primitives, as well as both retention-based and compacted topics. We discussed these attributes, in detail, earlier. Retention-based topics, which are immutable, provide a ‘version history’: the event sourced view. If you were storing products, the topic would hold every amendment made, in chronological order. Conversely, compacted topics provide the ‘latest’ view (i.e. the current product catalog without the ‘version history’). Very large datasets can be stored in Kafka. It is, after all, designed for ‘big data’ workloads, with production use cases of over one hundred terabytes not being uncommon in either retention-based or compacted forms. But to better understand Kafka’s use as an event store we need to delve a little deeper into (a) how services get data into the log and (b) how they query the data that is held there. Getting Events into the Log Event Sourcing At a high level, event sourcing is really just the observation that in an event driven architecture, the events are facts. So if you keep them around, you can use them as a datasource. One subtlety comes from the way the events are modelled. They can be values: whole facts (an Order, in its entirety) or they can be a set of ‘deltas’ that must be re-combined (a whole Order message, followed by messages denoting just the state changes: “amount updated to $5”, “order cancelled” etc). As an analogy, imagine you are building a version control system. When a user commits a file for the first time, you save it. Subsequent commits might only save the ‘delta’: just the lines that were added, changed or removed. Then, when the user performs a checkout, you open the version-0 file and apply all the deltas, to derive to the current state. The alternate approach is to simply store the whole file, exactly as it was at the time it was changed. This makes pulling out a version quick and easy, but to compare different versions you would have to perform a ‘diff’. The essence of event sourcing is the former approach: the current state is derived. People sometimes get hung up on this and they probably shouldn’t. Both approaches are perfectly valid, but come with slightly different tradeoffs in practice. Regardless of which one you pick, the important thing is to save facts exactly as they were observed, immutably. Implementing event sourcing in Kafka is a no-op if you’ve taken the event-driven approach. You simply write events as they occur. Sometimes you will have multiple output events which is where transactions become useful. But you do need to consider how to handle reads, as there are a few options, which are discussed in more detail below. Writing Through a Database into an Event Stream One way to get started with events is to write through a database table into a Kafka topic. The most reliable and efficient way to do this is using a technique called Change Data Capture (CDC). Most databases write every single insert, update and delete operation to a transaction log. This serves as a “source of truth” to the database and in case of errors, the database state is recovered from there. This means that we can also recreate the database state externally by copying the transaction log to Kafka, via Kafka’s Connect API, in a way that makes the events (insert, update, delete operations) accessible by other services. Different databases take slightly different approaches to this. Couchbase, which we’ve used in the example, uses a push-based mechanism, rather than long polling, but the end result is the same. In the example above, the Orders Service writes Orders to a database (Couchbase is used here but any CDC-enabled database will work). The writes are converted into an event stream by Kafka’s Connect API. This triggers downstream processing which validates the order. When the OrderValidated event returns to the Orders Service the database is updated with the final state of the order, before the call returns to the user. The advantage of this ‘database-fronted’ approach is it provides a consistency point: you write through it into Kafka, meaning you can always read your own writes. The pattern is also less error prone than write-aside, where your service writes to the database, then writes to the log—a pattern that requires distributed transactions for ensured accuracy. When we talk about streaming approaches later in this series, which embed storage inside our services, we’ll see they achieve an equivalent result. CDC isn’t available for every database, but the ecosystem is growing. Some popular databases currently supported for CDC in Kafka Connect are MySQL, Postgres, MongoDB and Cassandra. There are also proprietary CDC connectors for Oracle, IBM, SQL Server etc. The full list of connectors is here. Incrementally Evolve Away from Legacy Systems One of the most useful applications of Change Data Capture is in migrating from an old architecture to a new one. In reality, most projects have an element of legacy and renewal, and while there is a place for big-bang redesigns, incremental change is typically an easier pill to swallow. The problem with legacy is there is usually a good reason for moving away from it: the most common being that it is hard to change. Most business operations in legacy applications will converge on their database. This means that, no matter how creepy the legacy code is, the database provides a clean and coherent ‘seam’ to latch into the existing business workflow, from where we can extract events. Once we have the events, we can build new event-driven services that allow us to evolve away from the past, incrementally. So part of our legacy system might allow admins to manage and update the product catalog. We might retain this functionality by sucking the dataset into Kafka from the legacy system’s database. Then that product catalog can be reused in the validation service, or any other. An issue with attaching to legacy or externally source data is that the data is not always well formed. If this is a problem, consider adding a post processing stage. Kafka Connect’s single message transforms are useful for this type of operation, for example adding simple adjustments or enrichments—like adding information about the source—, while Kafka’s Streams API is ideal for simple to very complex manipulations and for pre-computing views that other services need. O’Reilly Book: Designing Event Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Creating Materialized Views In Memory Views Event sourcing is often used with Memory Images. This is just a fancy term, coined by Martin Fowler, for caching a whole dataset into memory—where it can be queried—rather than making use of an external database. Memory Images provide a simple and efficient model for datasets that (a) fit in memory and (b) can be loaded in a reasonable amount of time. For example, the validation service above might load one million products into memory, at say 100B each. This would take around 100MB of RAM and would load from Kafka in around a second on GbE. These days memory is typically easier to scale than network bandwidth so ‘worst case load time’ is often the more limiting factor of the two. To reduce the load time issue it’s useful to keep a snapshot of the event log using a compacted topic (which we discussed in a previous post). This represents the ‘latest’ set of events, without any of the ‘version history’. Memory Images provide an optimal combination of fast, in process reads, with write performance far faster and more scalable than a typical database. Thus it’s particularly well suited to high performance use cases. Derived Views created in a Database Kafka comes with several sink connectors that push changes into a wide variety of databases. This opens the architecture to a huge range of data stores, tuned to handle a variety of workloads. In the example here we use ElasticSearch for its full-text indexing capabilities, but whatever shape the problem there is probably a datastore that fits. Full Text search is added via an Elasticsearch database connected to the Orders topic via Kafka’s Connect API interface. This ability to quickly and efficiently translate the log into a range of different ‘views’ turns out to be a powerful attribute of its own. Later in the series we’ll look a lot more closely at this pattern, in particular the various service specific views we are able to create. Derived Views in KStreams State Stores Kafka’s Streams API provides the most complete abstraction for using event sourcing within JVM based services. A key element of this is the notion of State Stores. These are Key-Value databases (RocksDB by default) which are backed by Kafka topics. Services can read and write events via a state store. Events are flushed to Kafka and reloaded on startup. The State Store is backed by RocksDB (or hash table). You can write events to a State Store and they will be flushed to Kafka. Additionally, when a service starts, it will automatically populate its State Stores, so events can be queried from within the service. All the mechanics for this are handled for you. This is, however a far larger topic, so we will leave a detailed discussion to a later post. The Latest-Versioned Pattern A useful pattern, when using event sourcing in this way, is to hold events twice: once in a retention-based topic and once in a compacted topic. The retention-based topic will be larger as it holds the ‘version history’ of your data. The compacted topic is just the ‘latest’ view, and will be smaller, so it’s faster to load into a Memory Image or State Store. To keep the two topics in sync you can either dual write to them from your client (using a transaction to keep them atomic) or, more cleanly, use Kafka Streams to copy one into the other. Later in this series we’ll look at extending this pattern to create a whole variety of different types of views. The Truth Should be Shared Events are an important part of real world system design. They represent an invaluable trinity of notification, state distribution and decoupling. But Apache Kafka changes things. It pushes these three into a layer of permanence: messaging that remembers. So your applications get a choice: they can hit other services directly, and stay request driven, or embrace the event streams themselves; be it to drive their processing or extract whole datasets for their own private use. A value proposition that compounds as ecosystems grow and become more complex. As we develop these ideas further we are forced to question the very nature of data itself. To consider events not only as a source of shared truth, but as the only source of truth that matters. A shared narrative that underpins and safeguards the whole service estate. But if there is one killer feature of this architectural pattern it is its ability to evolve. The log breeds extensibility. Bootstrapping new services is a breeze with datasets always on hand, and always up to date. Migrating away from legacy, moving to new cloud platforms, new geographical regions, or simply building new services whose requirements have yet to be conceived, all these things are empowered by a service backbone that embraces data at its core. In the next post, we’ll take this argument a step further by rethinking estates of services using the lens of a database ‘turned inside out’. Leveraging the Power of a Database ‘Unbundled’ When you build microservices using Apache Kafka®, the log can be used as more than just a communication protocol. It can be used to store events: messaging that remembers. This leads to a single source of truth that spans estates of many independent services. We discussed this in the last post. But storage is just one piece of the puzzle. Adding stream processing into the mix allows a service estate to be rethought as a kind of distributed database: a database ‘unbundled.’ This has some interesting consequences, particularly for services that use datasets that are shared. Databases Aren’t Built to be Shared One of the problems with databases, at least in a microservices context, is that they cast an unusually rich type of coupling. This comes from the broad, amplifying interface that they expose to the outside world. So as services interact with this rich contract they get sucked in: service and database becoming tightly intertwined. This was covered in the first post, notably the cycle of data inadequacy. In the old days of monoliths, this didn’t matter. There were no nasty side effects, because when you released an application you released the database too, so it didn’t matter if the application was tightly coupled to the database or not: they both changed together. But if you are building a system of independently deployable services, all sharing a single database, when it comes to releasing one of them the broad, shared contract makes it hard to work out what effect your changes will have. In addition, as estates grow, databases become a central bottleneck meaning they get increasingly hard, and costly, to scale. Finally they represent shared mutable state. This leads to unpredictability. It also concentrates consistency into a single place. So sharing a database, in a service estate, is widely considered to be an antipattern[1]. Why would you want a Shared Database? You have Service APIs Service APIs provide a better option, but they come with problems of their own. They’re not good at wholesale data movement or anything remotely data intensive. Say you’re building a dashboard that displays order information, and each row involves a call to the Payment Service, the Orders Service, and the Customer Service. It’s probably going to be pretty sluggish as the user scrolls up and down. So what do you do? You might bring the data together ahead of time, caching it in memory or maybe using a database, but that leads to a bunch of issues around keeping this cache up to date and managing the process of polling the various data services. Another common solution is to have one database where everything ends up. This is usually given an ‘analytics’ or ‘warehousing’ badge, but the tight coupling a database emits is still there, so it remains a tricky resource to share as the number of services grows. So you really need a tool that brings together data so that it can be manipulated, while at the same time, not getting too entangled with other services. Let’s look at this idea of a database ‘unbundled.’ A Database ‘Unbundled’ A database is really a few concepts rolled into one: storage, indexing, caching, a query API, and sometimes transactionality to tie it all together. Unbundling a database is the idea that you can split these various concerns out into different layers, and there is benefit found in doing so. Jay Kreps described this back in 2013: “There is an analogy here between the role a log serves for data flow inside a distributed database and the role it serves for data integration in a larger organization… if you squint a bit, you can see the whole of your organization’s systems and data flows as a single distributed database. You can view all the individual query-oriented systems (Redis, SOLR, Hive tables, and so on) as just particular indexes on your data. You can view the stream processing systems like Storm or Samza as just a very well-developed trigger and view materialization mechanism. Classical database people, I have noticed, like this view very much because it finally explains to them what on earth people are doing with all these different data systems—they are just different index types!” This idea comes up in other areas too. The Clojure community talk about deconstructing the database. There are overlaps with event sourcing as was discussed in the last post. Martin Kleppmann also recast the concept, bringing it to life as a database turned inside out. The essence of this idea is twofold: you break out the various concepts found inside a database, these are then recomposed in a way that is more sympathetic to the system you are trying to build. So rethinking your systems in this way pushes them away from the black-box-style data management we get with traditional databases, where we route everything towards a single shared entity. Instead you’re encouraged to create independent, data-enabled components that can be composed alongside, or directly within your services. A good example is the materialised view. Reinventing Materialized Views and Embedding them Inside your Services In a database a materialised view is a query which the database runs and then caches. Whenever any of the underlying data changes, the materialised view is updated too. Database people use these to optimise read performance. This works because a view precomputes your query to get the data in exactly the right form for your use case. So when it comes to querying the view, all the heavy lifting has been done upfront. Pretty neat! The problem is that the materialised view only exists inside the database. But if you could pluck this concept out of the database and into your services you would have a continuously updated cache. Caches are used heavily in modern applications, so this seems like a powerful idea. It is also exactly what streaming platforms do. To unbundle a materialised view into a continuously updating cache we need three separate things: a mechanism for writing transactionally, a log that maintains an immutable journal of these writes, and a query engine that turns the journal into an index or view. Kafka plays the role of the journal and handles atomic writes, the Streams API plays the role of the query engine. But unlike a traditional materialised view these three elements are decentralised: they operate as independent entities. Being decentralised means the views can be placed anywhere. They might be a standalone entity. They might be embedded inside a service. The view can take any form you want, and can be regenerated on a whim. So we can summarise several benefits of this approach: (a) The Streams API provides a powerful DSL to define what data should be indexed. (b) The work to create the view is done upfront so it will be read optimised by default. (c) The view is embedded right inside our service so it’s fast and local. (d) Basing your system on a log, rather than a mutable database, avoids the unpredictability, or the concentration of consistency concerns, which come with shared mutable state. (e) The log also provides better performance characteristics both for reads and for writes. But unbundling goes one step further, when applied in a microservices context. It provides services with a kind of ‘database’ that they can share, one that avoids both an amplifying interface as well as shared mutable state. Coupling Coupling is a measure of the assumptions two services make about one another when they exchange information. Their interdependence. Loose Coupling is desirable as services with fewer dependencies will be easier to change. We looked at the relationship between services, databases and coupling in the first post. Unbundled Databases can be Shared Unlike a database, a log provides a low-coupling mechanism for sharing datasets. The loose coupling comes from the simple interface the log provides: little more than seek and scan, meaning the dominant source of coupling is the data itself. When a query layer is added on top of the log, say using Kafka Streams, services retain control of what queries are run, the hardware they run it on, what transformations are applied to create the views and most importantly: when and how these things are changed. It is this level of control, when compared to a traditional centralised database, that keeps services nimble and agile. When a service needs to release, all the highly coupled pieces are already inside it’s deployable unit. Only the data itself is shared. So an unbundled database has functional properties similar to a regular database, but without all of the bad parts. Create Service-Specific, Needs-Focussed Views One interesting consequence that comes with this style of system is that the views don’t need to be long-lived. If you take a copy of the product catalog and map it to your internal domain model, should that model change, you can just throw the view away and rebuild it. This is quite different to traditional messaging approaches where the messaging system is ephemeral, meaning any view you might create needs to be durable. It needs to be a system of record for that subscribing service. But with a distributed log messaging ‘remembers’, which means the views don’t have to. So the views can be ephemeral: simple caches that can be thrown away and rebuilt. The implications of this change in workflow go a little deeper still. Services are encouraged to take only the data they need, at a point in time. This keeps the views small and lightweight. It also reduces coupling. We will dive into this consequence more fully in the next post. Finally, the ease of creating views in a streaming platform means you can create more of them, specifically targeted to the task at hand. So as you develop this style of system you find yourself creating many service-specific views. A stark contrast to funneling all queries into a single, centralised database or data service. Example: Building an Embedded, Materialised Views using the Kafka Streams API Imagine you have a service that needs to combine Order and Product information, to print the Order Note that accompanies packages when they are mailed out from the warehouse to customers. The simplest way to do this would be to drive processing from new order events, enriching them by looking up the matching Product as they arrive. One option would be to call the Product service directly, but that would involve one network round trip per order. You can use Kafka’s Streams API to create a locally cached ‘materialised view’ so that you can look up products more efficiently. Once enriched the Order Note would be formatted, printed and mailed with the parcel. To implement such a view, you simply materialise the Products topic into a KTable. A KTable is a bit like a hashtable or dictionary mapping ProductId→Product. When the service starts, the Streams API will load the Products topic into the service and save it to a RocksDB instance (RocksDB is a small database used by Kafka Streams to create a view from a topic). The KTable is essentially a view over this RocksDB instance, embedded inside the service, and kept up to date as new messages come in. The code would look something like this: //Build a stream from the orders topic KStream ordersStream = builder.stream(..., “orders-topic”); //Build a table from the products topic, stored in a local state store called “product-store” GlobalKTable productsTable = builder.globalTable(...,“product-topic”,“product-store”); //Join the orders and products to do the enrichment KStream enrichedOrders = ordersStream.leftJoin(productsTable, (id, order) -> order.productId ...); In this case you’ve done an enrichment—combining orders and products—but it’s equally common to go the other way; culling data you don’t need or ignoring fields you don’t care about. This helps keep these local views lightweight and fast to regenerate. Example: Handling Request Response with Queryable State The previous example demonstrated how to look up Products from within a streaming service, in other words, from within the Kafka Streams processing thread, where you react to events and push your business processes forwards. But how do you manage queries that come from the outside: ‘what items are in my shopping basket?’, ‘what is the status of this order?”: problems that are inherently request-driven? User Interfaces are good examples as they tend to involve both commands and queries. Imagine you extend the previous example so that an administrator can view what Orders are leaving the warehouse via a web-based UI. You still want to join Orders and Products topics together, but you want to query that view from a browser. You would first fold the enriched orders into a table. This will flush them to a thing called a State Store. You can then query that state store using the Interactive Queries API. //Extending the example above (in the streams thread) push orders into their own topic. enrichedOrders.through(“enriched-orders-topic”); //Now build a table from that topic, which will be pushed into the “enriched-orders-store” KTable enrichedOrdersTable = builder.table(..., “enriched-orders-topic”, “enriched-orders-store”); //Inside our request-response thread (e.g. the webserver’s thread) ReadOnlyKeyValueStore ordersStore = streams.store(“enriched-orders-store”,...); ordersStore.get(orderId); This allows services to query the views that Kafka Streams creates. In the figure the view is made accessible as a REST endpoint, but you could plug in whatever communication protocol you like: gRPC, Websockets, GraphQL, Thrift etc. It’s also possible to scale a materialised view out horizontally, if there is too much data or if the network throughput is too high for a single server. When running in a distributed configuration Kafka Streams provides the discovery information necessary to route requests for a key to the appropriate server. Getting Started with Streaming Services The best way to get started with these patterns is to check out the KafkaMusic example. This is a little more complete than the ones described in this post. It creates a ‘streaming view’ of pop songs, along with how many times they have been played. The resulting ‘streaming view’ is exposed over a REST endpoint. To get started just download the Confluent Examples and use maven to install the various dependencies. There is a test which boots a Kafka cluster, so it’s easy to have a quick play around. Leveraging the Power of a Database and the Decoupling of an Event Stream At the heart of unbundling sits a simple idea. Rather than using a database, where you go to a single place for all your data needs, it is often preferable to break a database apart, then compose a solution from its constituent pieces, each finely tuned to do a certain job. The reason this is appealing is that the systems we build are much like this themselves: compositions of services that sit alone, typically do one thing well and form individual pieces of a more holistic puzzle. Kafka’s distributed log is a good example. Logs are a core element of most databases, although they’re not typically exposed to users. Their simplicity makes them fast and scalable. In a services context it also makes them well suited to sharing data: there is little to couple to, other than the data itself, and its immutability makes them predictable. In practice we may push the log into an indexed view, where many of the log’s benefits will be lost, but new ones will be gained. The value comes from being able to choose not just when, but also where this happens. This gives us flexibility. We might create a view that a browser can query; leveraging the ease with which we can shape the dataset to make the query fast and efficient. We might embed a materialised view close to some of our business logic, so we have an in-process, self-updating cache. We might use such a view because it gives us control, meaning a service can evolve and adapt without other services getting involved. So ‘unbundling’ is not about rethinking the database as such—databases are what they are—it’s about rethinking our programs and our data in the context of a distributed world. Remaining decoupled, but also keeping the ability to adapt and change. In the next post we’ll look at how we build a more fully fledged and stateful streaming application. Building a Microservices Ecosystem with Kafka Streams and KSQL BEN STOPFORD NOVEMBER 9, 2017 Today, we invariably operate in ecosystems: groups of applications and services which together work towards some higher level business goal. When we make these systems event-driven they come with a number of advantages. The first is the idea that we can rethink our services not simply as a mesh of remote requests and responses—where services call each other for information or tell each other what to do—but as a cascade of notifications, decoupling each event source from its consequences. The second comes from the realization that these events are themselves facts: a narrative that not only describes the evolution of your business over time, it also represents a dataset in its own right—your orders, your payments, your customers, or whatever they may be. We can store these facts in the very infrastructure we use to broadcast them, linking applications and services together with a central data-plane that holds shared datasets and keeps services in sync. These insights alone can do much to improve speed and agility, but the real benefits of streaming platforms come when we embrace not only the messaging backbone but the stream processing API itself. Such streaming services do not hesitate, they do not stop. They focus on the now—reshaping, redirecting, and reforming it; branching substreams, recasting tables, rekeying to redistribute and joining streams back together again. So this is a model that embraces parallelism not through brute force, but instead by sensing the natural flow of the system and morphing it to its whim. Life is a series of natural and spontaneous changes. Don’t resist them – that only creates sorrow. Let reality be reality. Let things flow naturally forward. Lao-Tzu Complex business systems can be built using the Kafka Streams API to chain a collection of asynchronous services together, connected via events. The differentiator here is the API itself, which is far richer than, say, the Kafka Producer or Consumer. It ships with native support for joining, summarising and filtering event streams, materializing tables, and it even encases the whole system with transparent guarantees of correctness. Systems built in this way, in the real world, come in a variety of guises. They can be fine-grained and fast executing, completing in the context of an HTTP request, or complex and long-running, manipulating the stream of events that map a whole company’s business workflow. This post focusses on the former, building up a real-world example of a simple order management system that executes within the context of an HTTP request, and is entirely built with Kafka Streams. Each service is a small function, with well-defined inputs and outputs. As we build this ecosystem up, we will encounter problems such as blending streams and tables, reading our own writes, and managing consistency in a distributed and asynchronous environment. Here is a picture of where we will end up (just to whet your appetite): O’Reilly Book: Designing Event-Driven Systems Explore all these concepts in detail with the free O’Reilly book “Designing Event-Driven Systems. Concepts and Patterns for Streaming Services with Apache Kafka” Your System has State: So Let’s Deal with It Making services stateless is widely considered to be a good idea. They can be scaled out, cookie-cutter-style, freed from the burdensome weight of loading data on startup. Web servers are a good example of this: to increase the capacity for generating dynamic content a web tier can be scaled horizontally, simply by adding new servers. So why would we want anything else? The rub is that most applications need state of some form, and this needs to live somewhere, so the system ends up bottlenecking on the data layer—often a database—sat at the other end of a network connection. In distributed architectures like microservices, this problem is often more pronounced as data is spread throughout the entire estate. Each service becomes dependent on the worst case performance and liveness of all the services it connects to. Caching provides a respite from this, but caching has issues of its own: invalidation, consistency, not knowing what data isn’t cached, etc. Streaming platforms come at this problem from a slightly different angle. Services can be stateless or stateful as they choose, but it’s the ability of the platform to manage statefulness—which means loading data into services—that really differentiates the approach. So why would you want to push data into your services? The answer is that it makes it easier to perform more data-intensive operations efficiently (the approach was invented to solve ultra-high-throughput streaming problems after all). To exemplify this point, imagine we have a user interface that allows users to browse Order, Payment, and Customer information in a scrollable grid. As the user can scroll through the items displayed, the response time for each row needs to be snappy. In a traditional, stateless model each row on the screen would require a call to all three services. This would be sluggish in practice, so caching would likely be added, along with some hand-crafted polling mechanism to keep the cache up to date. Using an event-streaming approach, we can materialize the data locally via the Kafka Streams API. We define a query for the data in our grid: “select * from orders, payments, customers where…” and Kafka Streams executes it, stores it locally, keeps it up to date. This ensures highly available should the worst happen and your service fails unexpectedly (this approach is discussed in more detail here). To combat the challenges of being stateful, Kafka ships with a range of features to make the storage, movement, and retention of state practical: notably standby replicas and disk checkpoints to mitigate the need for complete rebuilds, and compacted topics to reduce the size of datasets that need to be moved. So instead of pushing the data problem down a layer, stream processors are proudly stateful. They let data be physically materialized wherever it is needed, throughout the ecosystem. This increases performance. It also increases autonomy. No remote calls are needed! Of course, being stateful is always optional, and you’ll find that many services you build don’t require state. In the ecosystem we develop as part of this post; two are stateless and two are stateful, but the important point is that regardless of whether your services need local state or not, a streaming platform provisions for both. Finally, it’s also possible to control a stream processor running in a separate process using KSQL. This creates a hybrid pattern where your application logic can be kept stateless, separated from your stream processing layer, in much the same way that you might separate state from business logic using a traditional database. Coordination-Free by Design One important implication of pushing data into many different services is we can’t manage consistency in the same way. We will have many copies of the same data embedded in different services which, if they were writable, could lead to collisions and inconsistency. As this notion of ‘eventual consistency’ is often undesirable in business applications, one solution is to isolate consistency concerns (i.e. write operations) via the single writer principle. For example, the Orders Service would own how an Order evolves in time. Each downstream service then subscribes to the strongly ordered stream of events produced by this service, which they observe from their own temporal viewpoint. This adds an important degree of ‘slack’ into the system, decoupling services from one another in time and making it easier for them to scale and evolve independently. We will walk through an example of how this works in practice later in the post (the Inventory Service), but first, we need to look at the mechanics and tooling used to sew these ecosystems together. Using Kafka Streams & KSQL to Build a Simple Email Service Kafka Streams is the core API for stream processing on the JVM: Java, Scala, Clojure, etc. It is based on a DSL (Domain Specific Language) that provides a declaratively-styled interface where streams can be joined, filtered, grouped or aggregated (i.e. summarized) using the DSL. It also provides functionally-styled mechanisms — map, flatMap, transform, peek, etc.—so you can add bespoke processing of your own, one message one at a time. Importantly you can blend these two approaches together, with the declarative interface providing a high-level abstraction for SQL-like operations and the more functional methods adding the freedom to branch out into any arbitrary code you may wish to write. But what if you’re not running on the JVM, or you want to do stateful stream processing in a separate process (say, to keep your application logic stateless)? In this case, you’d use KSQL. KSQL provides a simple, interactive SQL interface for stream processing and can be run standalone and controlled remotely. KSQL utilizes the Kafka Streams API under the hood, meaning we can use it to do the same kind of declarative slicing and dicing we might do in JVM code using the Streams API. Then a native Kafka client, in whatever language our service is built in, can process the manipulated streams one message at a time. Whichever approach we take, these tools let us model business operations in an asynchronous, non-blocking, and coordination-free manner. Let’s consider something concrete. Imagine we have a service that sends emails to platinum-level clients (this fits into the ecosystem at the top of the above system diagram). We can break this problem into two parts: firstly we prepare the data we need by joining a stream of Orders to a table of Customers and filtering for the ‘Platinum’ clients. Secondly, we need code to construct and send the email itself. We would do the former in the DSL and the latter with a per-message function: //Filter input stream then send Email orders.join(customers, Tuple::new) //join customers and orders .filter((k, tuple) → tuple.customer.level().equals(PLATINUM) //filter platinum customers && tuple.order.state().equals(CONFIRMED)) //only consider confirmed orders .peek((k, tuple) → emailer.sendMail(tuple)); //send email for each cust/order tuple A more fully-fledged Email service can be found in the microservice code examples. We also extend this Transform/Process pattern later in the Inventory Service example discussed later in this post. An equivalent operation can be performed, off the JVM, using KSQL. The pattern is the same: the event stream is dissected with a declarative statement, then processed one record at a time. Here we implement the emailer in Node.js with KSQL running via the Sidecar Pattern: //Execute query in KSQL sidecar to filter stream ksql> CREATE STREAM orders (ORDERID string, ORDERTIME bigint...) WITH (kafka_topic='orders', value_format='JSON'); ksql> CREATE STREAM platinum_emails as select * from orders, customers where client_level == ‘PLATINUM’ and state == ‘CONFIRMED’; //In Node.js service send Email var nodemailer = require('nodemailer'); … var kafka = require('kafka-node'), Consumer = kafka.Consumer, client = new kafka.Client(), consumer = new Consumer(client, [ { topic: 'platinum_emails', partition: 0 } ] ); consumer.on('message', function (orderConsumerTuple) { sendMail(orderConsumerTuple); }); The Tools of the Trade: Windows, Tables & State Stores Before we develop more complex microservice example let’s take a look more closely at some of the key elements of the Kafka Streams API. Kafka Streams needs its own local storage for a few different reasons. The most obvious is for buffering, as unlike in a traditional database—which keeps all historical data on hand—stream operations only need to collect events for some period of time. One that corresponds to how ‘late’ related messages may be with respect to one another. Let’s use a few variants on the email example. Imagine you want to send an email that confirms payment of a new order. We know that an Order and its corresponding Payment will turn up at around the same time, but we don’t know for sure which will come first or exactly how far apart they may be. We can put an upper limit on this though—let’s say an hour to be safe. To avoid doing all of this buffering in memory, Kafka Streams implements disk-backed State Stores to overflow the buffered streams to disk (think of this as a disk-resident hashtable). So each stream is buffered in this State Store, keyed by its message key. Thus, regardless of how late a particular event may be, the corresponding event can be quickly retrieved. Kafka Streams takes this same concept a step further to manage whole tables. Tables are a local manifestation of a complete topic—usually compacted—held in a state store by key. (You can also think of them as a stream with infinite retention.) In a microservices context, such tables are often used for enrichment. Say we decide to include Customer information in our Email logic. We can’t easily use a stream-stream join as there is no specific correlation between a user creating an Order and a user updating their Customer Information—that’s to say that there is no logical upper limit on how far apart these events may be. So this style of operation requires a table: the whole stream of Customers, from offset 0, replayed into the State Store inside the Kafka Streams API. The nice thing about using a KTable is it behaves like a table in a database. So when we join a stream of Orders to a KTable of Customers, there is no need to worry about retention periods, windows or any other such complexity. If the customer record exists, the join will just work. There are actually two types of table in Kafka Streams: KTables and Global KTables. With just one instance of a service running, these effectively behave the same. However, if we scaled our service out—so it had, say, four instances running in parallel—we’d see slightly different behavior. This is because Global KTables are cloned: each service instance gets a complete copy of the entire table. Regular KTables are sharded: the dataset is spread over all service instances. So in short, Global KTables are easier to use, but they have scalability limits as they are cloned across machines, so use them for lookup tables (typically up to several gigabytes) that will fit easily on a machine’s local disk. Use KTables, and scale your services out, when the dataset is larger. The final use of the State Store is to save information, just like we might write data to a regular database. This means we can save any information we wish and read it back again later, say after a restart. So we might expose an Admin interface to our Email Service which provides stats on emails that have been sent. We could store these stats in a state store and they’ll be saved locally, as well as being backed up to Kafka, inheriting all its durability guarantees. Scalable, Consistent, Stateful Operations in the Inventory Service Now we have a basic understanding of the tools used in stream processing, let’s look at how we can sew them together to solve a more complex and realistic use case—one which requires that reads and writes are performed consistently, at scale. In the system design diagram, there is an Inventory Service. When a user makes a purchase—let’s say it’s an iPad—the Inventory Service makes sure there are enough iPads in stock for the order to be fulfilled. To do this a few things need to happen as a single atomic unit. The service needs to check how many iPads there are in the warehouse. Next, one of those iPads must be reserved until such time as the user completes their payment, the iPad ships, etc. This requires four actions to be performed inside each service instance as a single, atomic transaction: Read an Order message Validate whether there is enough stock available for that Order: (iPads in the warehouse KTable) – (iPads reserved in the State Store). Update the State Store of “reserved items” to reserve the iPad so no one else can take it. Send out a message that Validates the order. You can find the code for this service here. What is neat about this approach is its ability to scale these atomic operations out across many threads or machines. There is no remote locking, there are no remote reads. This works reliably because the code ensures two things: Kafka’s transactions feature is enabled. Data is partitioned by ProductId before the reservation operation is performed. The first point should be obvious. Kafka’s transactions ensure atomicity. Partitioning by ProductId is a little more subtle. Partitioning ensures that all orders for iPads are sent to a single thread in one of the available service instances, guaranteeing in order execution. Orders for other products will be sent elsewhere. So in a distributed deployment, this guarantees in-order execution for orders for the same type of product, iPads, iPhones, etc., without the need for cross-network coordination. (For more detail see the section “Scaling Concurrent Operations in Streaming Systems” in the book Designing Event-Driven Systems.) Bridging the Synchronous and Asynchronous Worlds Finally, we can put all these ideas together in a more comprehensive ecosystem that validates and processes orders in response to an HTTP request, mapping the synchronous world of a standard REST interface to the asynchronous world of events, and back again. Code Examples Download and play with the full code example for this Microservices ecosystem from Github Looking at the Orders Service first, a REST interface provides methods to POST and GET Orders. Posting an Order creates an event in Kafka. This is picked up by three different validation engines (Fraud Check, Inventory Check, Order Details Check) which validate the order in parallel, emitting a PASS or FAIL based on whether each validation succeeds. The result of each validation is pushed through a separate topic, Order Validations, so that we retain the ‘single writer’ status of the Orders Service —> Orders Topic. The results of the various validation checks are aggregated back in the Order Service (Validation Aggregator) which then moves the order to a Validated or Failed state, based on the combined result. This is essentially an implementation of the Scatter-Gather design pattern. To allow users to GET any order, the Orders Service creates a queryable materialized view (‘Orders View’ in the figure), using a state store in each instance of the service, so any Order can be requested historically. Note also that the Orders Service is partitioned over three nodes, so GET requests must be routed to the correct node to get a certain key. This is handled automatically using the Interactive Queries functionality in Kafka Streams, although the example has to implement code to expose the HTTP endpoint. (Alternatively, we could also implement this view with an external database, via Kafka Connect.) The example also includes code for a blocking HTTP GET so that clients have the option of reading their own writes (i.e. avoiding the race conditions that come with eventual consistency). In this way we bridge the synchronous, blocking paradigm of a Restful interface with the asynchronous, non-blocking processing performed server-side: # Submit an order. Immediately retrieving it will block until validation completes. $ curl -X POST ... --data {"id":"1"...} http://server:8081/orders/ $ curl -X GET http://server:8081/orders/validated/1?timeout=500 Finally, there are two other interesting services in the example code. The Fraud Service tracks the total value of orders for each customer in a one-hour window, alerting if the configured fraud limit is exceeded. This is implemented entirely using the Kafka Streams DSL, although it could be implemented in custom code via a Transformer also. The Order Details Service validates the basic elements of the order itself. This is implemented with a producer/consumer pair, but could equally be implemented using Kafka Streams. When to Apply Architectures of this Type This example is about as fine-grained as streaming services get, but it is useful for demonstrating the event-driven approach and how that is interfaced into the more familiar synchronous request-response paradigm via event collaboration. (We discussed the merits of event collaboration in an earlier post.) But we should look at any architectural change with a critical eye. If we’re building systems for the synchronous world, where users click buttons and wait for things to happen, there may be no reason to change. The architecture described here is more complex than many simple CRUD systems. That’s to say the baseline cost is higher, both in complexity and in latency. This tradeoff is worth making if the system we are building needs to grow and evolve significantly in the future. Notably, incorporating different teams, as well as offline services that do not require a response go immediately back to the user: re-pricing, fulfillment, shipping, billing, notifications, etc. In these larger, distributed ecosystems, the pluggability, extensibility, and decoupling that comes with the event-driven, brokered approach increasingly pay dividends as the ecosystem grows. So the beauty of implementing services on an Event Streaming Platform lies in its ability to handle both the micro and the macro with a single, ubiquitous workflow. Here, fine-grained use cases merge into larger architectures that span departments, companies, and geographies. Summing Up When we build services using a Streaming Platform, some will be stateless: simple functions that take an input, perform a business operation and produce an output. Some will be stateful, but read-only, as in when views need to be created so we can serve remote queries. Others will need to both read and write state, either entirely inside the Kafka ecosystem (and hence wrapped in Kafka’s transactional guarantees), or by calling out to other services or databases. Having all approaches available makes the Kafka’s Streams API a powerful tool for building event-driven services. But there are of course drawbacks to this approach. Whilst standby replicas, checkpoints, and compacted topics all mitigate the risks of pushing data to code, there is always a worst-case scenario where service-resident datasets must be rebuilt, and this should be considered as part of any system design. There is also a mindset shift that comes with the streaming model, one that is inherently asynchronous and adopts a more functional style, when compared to the more procedural style of service interfaces. But this is—in the opinion of this author—an investment worth making. So hopefully the example described in this post is enough to introduce you to what event streaming microservices are about. We looked at how a small ecosystem can be built through the propagation of business events that describe the order management workflow. We saw how such services can be built on the JVM with Kafka’s Streams API, as well as off the JVM via KSQL. We also looked more closely at how to tackle trickier issues like consistency with writable state stores and change logs. Finally, we saw how simple functions, which are side-effect-free, can be composed into service ecosystems that operate as one. In the next post in this series, Bobby Calderwood will be taking this idea a step further as he makes a case for a more functional approach to microservices through some of the work done at Capital One.
Unstarred
🛁 Dunked (excluded from study) — Undunk
Tags
General
Learning
Life
Strategy
Planning
Training
Risk
Problem Solving
Behavior Change
Business
Financial Planning
Startups
Personal Finance
Relationships
Financial Strategy
Finance
Software
Software Development/Design
Diet/Fitness
Games
Thinking
Investing
Self-Esteem
Sales
Physiology
Emotion
Writing
Creativity
Narratives
Time
Working
Pickup
Social Dynamics
Negotiation
Parenting
Marketing
Databases
Scaling
Software Security
Software Architecture
Interrogation
UI/UX
Fantasy Football
Grooming
Future Planning
Products
Cognitive Behavioral Therapy
Networking
Comedy
Web Design
Naval
Decision Making
Systems Thinking
Mindsets
Math
Analytics
Careers
Goal Setting
Culture
Habits
Resistance
Kafka
Docs
wri
Copywriting
Frameworks
Checklists
Digital Strategy
Data
Digital Transformation
Information Science
Product
Critical Thinking
Content Marketing
SEO
Rory Sutherland
Psychology
Content Creation
Differentiation
Capital Allocation
Distributed Systems
Storytelling
Systems
Behavior
Effectiveness
Big Data
Integrity
Productivity
Work Ethic
Discipline
Questions
Interviews
Life Planning
Failure
Success
Expertise
Positioning
Dating
Betting
Frames
Power
Office Politics
Growth
Personal Growth
Data Engineering
Rules
Microservices
Reading
Laravel
Organizations
Product Development
Ideation
Communication
Design
Wealth
Entrepreneurship
Customer Development
Writ
Recipes
Health
Mastery
Management
Product-Led Growth
Tech
Stream Processing
Time Management
Prioritization
produ
SaaS
Confluent
Customer Success
Mindfulness
Leadership
Operator
Network Theory
UAI
HTBAGASPB
MOTHA
MOTHA-U
HTBAGASPB-O
MOTHA-A
HTBAGASPB-P
HTBAGASPB-S
MOTHA-I
HTBAGASPB-M
Sex
Project Management
Leverage
knowledge|1195
knowledge|1033
knowledge|442
knowledge|938
knowledge|358
knowledge|875
Process
statistical proccess control
Chess
Performance
Focus
Screenshot Essay
AI
Prompting
Reasoning Through Feeling
Virtues
AI Agents
Economics
Identity
Evolution
Execution
Philosophy
Trueridge
Agents
Cancer
Trading
Stock Market
Neuroscience
Pain
Discomfort
ADHD
Creative
Art
Rating
Great
OK
Incomplete
Get Help
Ask a Question
Submit
Previous Answers
Review Questions
help questions
Submit
Todos
Submit
Sensemaker
Sensemaker
× Close