Logo

dev-resources.site

for different kinds of informations.

From Chaos to Control

Published at
1/3/2025
Categories
clojure
cleancode
cljasync
clj
Author
borba
Categories
4 categories in total
clojure
open
cleancode
open
cljasync
open
clj
open
Author
5 person written this
borba
open
From Chaos to Control

In modern software development, scalability and resilience are not luxuries—they are essential requirements. As online traffic grows and user expectations increase, building software that can handle traffic spikes and respond efficiently is more critical than ever.

Imagine payment systems processing thousands of transactions per second or applications managing millions of simultaneous accesses. A real-world example of such pressure occurred in 2020 in Brazil during the COVID-19 pandemic. Millions of Brazilians attempted to access "Caixa Econômica Federal's" app to claim emergency aid. The system experienced massive overload, virtual queues lasting hours, and complete failures in many cases. This scenario highlights the importance of designing robust systems prepared for extreme situations from the outset.

In the Clojure ecosystem, core.async emerges as a powerful tool to tackle these challenges. Inspired by the CSP (Communicating Sequential Processes) model, it enables developers to build highly concurrent systems elegantly and functionally.

But, Why core.async?

Resilient and scalable systems require tools that simplify concurrency management, data flow control, and asynchronous operations. With core.async, you can:

  • Create non-blocking pipelines that process data efficiently.
  • Compose asynchronous operations intuitively.
  • Manage complex data flows using thread-safe channels.
  • Build systems that scale horizontally and remain responsive under high load.

Consider designing a payment system that needs to process thousands of transactions per second, integrate with multiple external services, and still ensure fast responses to users. core.async provides the tools to meet these requirements while keeping the code clean and modular.

Fundamental Concepts of core.async

Channels: The Heart of core.async

Channels are the foundation of core.async. They enable safe communication between different parts of your program. Here are examples of how to configure them:

(ns example.core
  (:require [clojure.core.async :refer [chan buffer sliding-buffer dropping-buffer]]))

;; Basic channel
(def simple-channel (chan))

;; Channel with a fixed buffer
(def buffered-channel (chan (buffer 10)))

;; Sliding buffer channel (drops older elements)
(def sliding-channel (chan (sliding-buffer 10)))

;; Dropping buffer channel (discards new elements when full)
(def dropping-channel (chan (dropping-buffer 10)))

Enter fullscreen mode Exit fullscreen mode

Operations: Put and Take
Put (>! and >!!)
Used to send data to a channel. Non-blocking operations are performed inside go blocks.

(ns example.puts
  (:require [clojure.core.async :refer [chan go >!]]))

(go
  (let [channel (chan)]
    (>! channel "message")
    (println "Message sent!")))

Enter fullscreen mode Exit fullscreen mode

Take (<! and <!!)
Used to receive data from a channel, typically inside go blocks.

(ns example.takes
  (:require [clojure.core.async :refer [chan go <!]]))

(go
  (let [channel (chan)]
    (println "Received:" (<! channel))))

Enter fullscreen mode Exit fullscreen mode

Advanced Operations

alts! - Selecting from Multiple Channels
Ideal for scenarios requiring timeouts or fallback between multiple operations.

(ns example.alts
  (:require [clojure.core.async :refer [chan go alts! timeout]]))

(go
  (let [result-channel (chan)
        timeout-channel (timeout 5000)]
    (let [[result channel] (alts! [result-channel timeout-channel])]
      (if (= channel timeout-channel)
        (println "Timeout!")
        (println "Result received:" result)))))

Enter fullscreen mode Exit fullscreen mode

Practical Example: Payment Processing System

Let's build a system that validates, processes, and notifies payments resiliently:

(ns system.payments
  (:require [clojure.core.async :refer [chan go go-loop >! <! timeout buffer close! <!!]]))

(defn validate-payment [payment]
  (go
    (<! (timeout 100))
    (if (>= (:balance payment) (:amount payment))
      (assoc payment :status :validated)
      (assoc payment :status :insufficient-balance))))

(defn process-payment [payment]
  (go
    (<! (timeout 150))
    (assoc payment :status :processed)))

(defn notify-user [payment]
  (go
    (<! (timeout 50))
    (assoc payment :notified true)))

(defn create-payment-system []
  (let [input (chan (buffer 1000))
        validated (chan)
        processed (chan)
        notifications (chan)
        errors (chan)
        done (chan)]

    ;; Validation
    (go-loop []
      (when-let [payment (<! input)]
        (let [result (<! (validate-payment payment))]
          (if (= (:status result) :validated)
            (>! validated result)
            (>! errors result))
          (recur))))

    ;; Parallel Processing
    (dotimes [_ 5]
      (go-loop []
        (when-let [payment (<! validated)]
          (let [processed-payment (<! (process-payment payment))]
            (>! processed processed-payment)
            (recur)))))

    ;; Notifications
    (go-loop []
      (when-let [payment (<! processed)]
        (>! notifications (<! (notify-user payment)))
        (recur)))

    (go
      (<! (timeout 3000))
      (close! validated)
      (close! processed)
      (close! notifications)
      (close! errors)
      (>! done true))

    {:input input
     :notifications notifications
     :errors errors
     :done done}))

(defn demo []
  (let [{:keys [input notifications errors done]} (create-payment-system)
        processed-count (atom 0)
        error-count (atom 0)]

    ;; Notification monitor
    (go-loop []
      (when-let [notification (<! notifications)]
        (println "Payment processed:" notification)
        (swap! processed-count inc)
        (recur)))

    ;; Error monitor
    (go-loop []
      (when-let [error (<! errors)]
        (println "Payment error:" error)
        (swap! error-count inc)
        (recur)))

    ;; Generate payments
    (go
      (doseq [i (range 10)]
        (>! input {:id i
                   :amount (rand-int 1000)
                   :balance (rand-int 1000)})
        (<! (timeout 100)))
      (close! input))

    done))

Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Appropriate Buffers:
    Avoid excessively large buffers to prevent high memory consumption.
    Use smaller buffers to prevent backpressure.

  2. Error Handling:
    Always dedicate channels for errors.
    Implement retry mechanisms and circuit breakers where needed.

  3. Channel Closing:
    Close channels gracefully.
    Handle closed channels properly to avoid runtime errors.

  4. Monitoring and Metrics:
    Track queue sizes and processing times.
    Integrate monitoring tools for real-time visibility.

Conclusion

Building resilient and scalable systems is essential to meet the demands of an increasingly digital world. With core.async, you can create elegant solutions that scale horizontally, remain responsive under heavy loads, and fail gracefully.

Whether for small experiments or critical systems, core.async is a valuable addition to any Clojure developer’s toolkit. Explore the provided examples and start building systems ready for the future today!

cleancode Article's
30 articles in total
Favicon
STOP Writing Dirty Code: Fix The Data Class Code Smell Now!
Favicon
Абстракции vs. привязка к технологии
Favicon
An Initiation to Domain-Driven Design
Favicon
7 Essential Design Patterns for JavaScript Developers: Boost Your Coding Mastery
Favicon
Orden en el Código .NET
Favicon
Movie X: A Developer’s Dive Into Flutter Project Organization
Favicon
3 Code Comment Mistakes You're Making Right Now
Favicon
From Chaos to Control
Favicon
Clean code
Favicon
Want to Learn Docker in Advance Way?
Favicon
3 very simple React patterns to immediately improve your codebase 🪄
Favicon
Refactoring 021 - Remove Dead Code
Favicon
Code Commenting Ethics: When Over-Documentation Hurts Development
Favicon
Clean Code: Managing Side Effects with Functional Programming
Favicon
Why Use Getters and Setters?!
Favicon
Clojure Is Awesome!!! [PART 4]
Favicon
Clojure Is Awesome!!! [PART 3]
Favicon
Python's Magic Methods
Favicon
Clojure Is Awesome!!! [PART 2]
Favicon
Why should I care about Quality? I'm a developer!
Favicon
Mastering Laravel Blade: @stack, @push, and @endpush
Favicon
How to write a good Clean code? - Tips for Developers with Examples
Favicon
Union and Intersection Types in TypeScript
Favicon
Arquitetura Viva: Moldando Sistemas para Mudanças
Favicon
Dependency Injection in ASP.NET Core with Extension Classes: A Comprehensive Guide
Favicon
Rails transactional callbacks beyond models
Favicon
Python Best Practices: Writing Clean and Maintainable Code
Favicon
Excited to Be Part of This Community! 🚀
Favicon
Single Responsibility Principle in Javascript
Favicon
Build Express APIs Faster than AI

Featured ones: