<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bartek Żyliński</title>
    <description>The latest articles on DEV Community by Bartek Żyliński (@pasksoftware).</description>
    <link>https://dev.to/pasksoftware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3437044%2F16687e65-fc42-4839-bd2c-0c40938462a2.jpg</url>
      <title>DEV Community: Bartek Żyliński</title>
      <link>https://dev.to/pasksoftware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pasksoftware"/>
    <language>en</language>
    <item>
      <title>Need to enforce “Package X must never depend on Y”? Just assert it. ArchUnit converts architectural rules into JUnit tests, breaking the build before review. #architecture #testing</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Mon, 25 Aug 2025 15:42:05 +0000</pubDate>
      <link>https://dev.to/pasksoftware/need-to-enforce-package-x-must-never-depend-on-y-just-assert-it-archunit-converts-architectural-4dpl</link>
      <guid>https://dev.to/pasksoftware/need-to-enforce-package-x-must-never-depend-on-y-just-assert-it-archunit-converts-architectural-4dpl</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao" class="crayons-story__hidden-navigation-link"&gt;ArchUnit Guide – How to Unit Test Your Architecture&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pasksoftware" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3437044%2F16687e65-fc42-4839-bd2c-0c40938462a2.jpg" alt="pasksoftware profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pasksoftware" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Bartek Żyliński
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Bartek Żyliński
                
              
              &lt;div id="story-author-preview-content-2775671" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pasksoftware" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3437044%2F16687e65-fc42-4839-bd2c-0c40938462a2.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Bartek Żyliński&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao" id="article-link-2775671"&gt;
          ArchUnit Guide – How to Unit Test Your Architecture
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwareengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwareengineering&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/archunit"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;archunit&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/testing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;testing&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>java</category>
      <category>softwareengineering</category>
      <category>archunit</category>
      <category>testing</category>
    </item>
    <item>
      <title>Chatting with Spring &amp; WebSocket</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Mon, 11 Aug 2025 18:55:27 +0000</pubDate>
      <link>https://dev.to/pasksoftware/chatting-with-spring-websocket-4jo2</link>
      <guid>https://dev.to/pasksoftware/chatting-with-spring-websocket-4jo2</guid>
      <description>&lt;p&gt;As you may have already guessed from the title the topic for today will be Spring Boot WebSockets. Some time ago I provided an example of WebSocket chat based on &lt;a href="https://dzone.com/articles/writing-a-chat-with-akka" rel="noopener noreferrer"&gt;Akka toolkit&lt;/a&gt; libraries. However, this chat will have somewhat more features, and a quite different design.&lt;/p&gt;

&lt;p&gt;I will skip some parts so as not to duplicate too much content from the previous article. Here you can find a more in depth intro &lt;a href="https://pasksoftware.com/websockets-vs-sse/" rel="noopener noreferrer"&gt;into WebSockets&lt;/a&gt;. Please note that all the code that’s used in this article is also available in the &lt;a href="https://github.com/Pask423/articles-misc/tree/master/spring-ws" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Boot WebSocket – Tools Used
&lt;/h2&gt;

&lt;p&gt;Let’s start the technical part of this text with a description of tools that will be further used to implement the whole application. As I cannot fully grasp how to build real WebSocket API with classic Spring — &lt;a href="https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html" rel="noopener noreferrer"&gt;STOMP overlay&lt;/a&gt;. I decided to go for &lt;a href="https://docs.spring.io/spring-framework/reference/web/webflux.html" rel="noopener noreferrer"&gt;Spring WebFlux&lt;/a&gt; and make everything reactive.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spring Boot&lt;/strong&gt; — no modern Java app based on Spring can exist without Spring Boot, all the autoconfiguration is priceless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring WebFlux&lt;/strong&gt; — reactive version of classic Spring, provides quite a nice and descriptive toolkit for handling both WebSockets and REST. I would dare to say that it is the only way to actually get WebSocket support in Spring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mongo&lt;/strong&gt; — one of the most popular NoSQL databases, I am using it for storing messages history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Reactive Mongo&lt;/strong&gt; — Spring Boot starter for handling Mongo access in reactive fashion. Using reactive in one place but not the other is not the best idea. Thus, I decided to make DB access reactive as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start the implementation!&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Boot WebSocket – Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dependencies and config.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  pom.xml
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;!--Compile--&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-webflux&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-data-mongodb-reactive&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  application.properties
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring.data.mongodb.uri=mongodb://chats-admin:admin@localhost:27017/chats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer &lt;code&gt;.properties&lt;/code&gt; over &lt;code&gt;.yml&lt;/code&gt; — IMHO YAML is not readable and non-maintainable on a bigger scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocketConfig
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Configuration
class WebSocketConfig {

    @Bean
    ChatStore chatStore(MessagesStore messagesStore) {
        return new DefaultChatStore(Clock.systemUTC(), messagesStore);
    }

    @Bean
    WebSocketHandler chatsHandler(ChatStore chatStore) {
        return new ChatsHandler(chatStore);
    }

    @Bean
    SimpleUrlHandlerMapping handlerMapping(WebSocketHandler wsh) {
    Map&amp;lt;String, WebSocketHandler&amp;gt; paths = Map.of("/chats/{id}", wsh);
        return new SimpleUrlHandlerMapping(paths, 1);
    }

    @Bean
    WebSocketHandlerAdapter webSocketHandlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And surprise, all the four beans defined here are very important.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ChatStore&lt;/strong&gt; — custom bean for operating on chats, I will go into more details on this bean in following steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocketHandler&lt;/strong&gt; — this bean will store all the logic related to handling WebSockets sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SimpleUrlHandlerMapping&lt;/strong&gt; — responsible for mapping urls to correct handler full url for this one will look more or less like this &lt;code&gt;ws://localhost:8080/chats/{id}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocketHandlerAdapter&lt;/strong&gt; — a kind of capability bean it adds WebSockets handling support to Spring Dispatcher Servlet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ChatsHandler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ChatsHandler implements WebSocketHandler {

    private final Logger log = LoggerFactory.getLogger(ChatsHandler.class);

    private final ChatStore store;

    ChatsHandler(ChatStore store) {
      this.store = store;
    }

    @Override
    public Mono handle(WebSocketSession session) {
        String[] split = session.getHandshakeInfo()
            .getUri()
            .getPath()
            .split("/");
        String chatIdStr = split[split.length - 1];
        int chatId = Integer.parseInt(chatIdStr);
        ChatMeta chatMeta = store.get(chatId);
        if (chatMeta == null) {
            return session.close(CloseStatus.GOING_AWAY);
        }
        if (!chatMeta.canAddUser()) {
            return session.close(CloseStatus.NOT_ACCEPTABLE);
        }

        String sessionId = session.getId();
        store.addNewUser(chatId, session);
        log.info("New User {} join the chat {}", sessionId, chatId);
        return session
               .receive()
               .map(WebSocketMessage::getPayloadAsText)
               .flatMap(message -&amp;gt; store.addNewMessage(chatId, sessionId, message))
               .flatMap(message -&amp;gt; broadcastToSessions(sessionId, message, store.get(chatId).sessions())
               .doFinally(sig -&amp;gt; store.removeSession(chatId, session.getId()))
               .then();
    }

    private Mono broadcastToSessions(String sessionId, String message, List sessions) {
        return sessions
        .stream()
        .filter(session -&amp;gt; !session.getId().equals(sessionId))
        .map(session -&amp;gt; session.send(Mono.just(session.textMessage(message))))
        .reduce(Mono.empty(), Mono::then);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned above here you can find all the logic related to handling WebSocket sessions. First we parse the ID of a chat from the url to get target chat. Responding with different statuses depend on the context present for a particular chat.&lt;/p&gt;

&lt;p&gt;Additionally, I am also broadcasting the message to all the sessions related to particular chat — for users to actually exchange the messages. I have also added &lt;code&gt;doFinally&lt;/code&gt; trigger that will clear closed sessions from the &lt;strong&gt;chatStore&lt;/strong&gt; , to reduce redundant communication.&lt;/p&gt;

&lt;p&gt;As whole this code is reactive there are some restrictions I need to follow. I have tried to make it as simple and readable as possible, if you have any idea how to improve it I am open.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatsRouter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
    class ChatRouter {

    private final ChatStore chatStore;

    ChatRouter(ChatStore chatStore) {
        this.chatStore = chatStore;
    }

    @Bean
    RouterFunction routes() {
        return RouterFunctions
        .route(POST("api/v1/chats/create"), e -&amp;gt; create(false))
        .andRoute(POST("api/v1/chats/create-f2f"), e -&amp;gt; create(true))
        .andRoute(GET("api/v1/chats/{id}"), this::get)
        .andRoute(DELETE("api/v1/chats/{id}"), this::delete);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebFlux approach to defining REST endpoints is quite different from the classic Spring. Above you can see the definition of 4 endpoints for managing chats. As similar as in the case of Akka implementation I want to have a REST API for managing Chats and WebSocket API for actual handling chats.&lt;/p&gt;

&lt;p&gt;I will skip the functions implementations as they are pretty trivial, you can see them on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatStore
&lt;/h3&gt;

&lt;p&gt;First the interface&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface ChatStore {

    int create(boolean isF2F);

    void addNewUser(int id, WebSocketSession session);

    Mono addNewMessage(int id, String userId, String message);

    void removeSession(int id, String session);

    ChatMeta get(int id);

    ChatMeta delete(int id);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the implementation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class DefaultChatStore implements ChatStore {

    private final Map&amp;lt;Integer, ChatMeta&amp;gt; chats;
    private final AtomicInteger idGen;
    private final MessagesStore messagesStore;
    private final Clock clock;

    public DefaultChatStore(Clock clock, MessagesStore store) {
        this.chats = new ConcurrentHashMap&amp;lt;&amp;gt;();
        this.idGen = new AtomicInteger(0);
        this.clock = clock;
        this.messagesStore = store;
    }

    @Override
    public int create(boolean isF2F) {
        int newId = idGen.incrementAndGet();
        ChatMeta chatMeta = chats.computeIfAbsent(newId, id -&amp;gt; {
        if (isF2F) {
            return ChatMeta.ofId(id);
        }
            return ChatMeta.ofIdF2F(id);
        });
        return chatMeta.id;
    }

    @Override
    public void addNewUser(int id, WebSocketSession session) {
        chats.computeIfPresent(id, (k, v) -&amp;gt; v.addUser(session));
    }

    @Override
    public void removeSession(int id, String sessionId) {
      chats.computeIfPresent(id, (k, v) -&amp;gt; v.removeUser(sessionId));
    }

    @Override
    public Mono addNewMessage(int id, String userId, String message) {
        ChatMeta meta = chats.getOrDefault(id, null);
        if (meta != null) {
            Message messageDoc = new Message(id, userId, meta.offset.getAndIncrement(), clock.instant(), message);
            return messagesStore.save(messageDoc)
                    .map(Message::getContent);
        }
        return Mono.empty();
    }
    // omitted
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base of ChatStore is the ConcurrentHashMap that holds the metadata of all open chats. Most of the methods from the interface are self-explanatory and there is nothing special behind them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create&lt;/code&gt; -&amp;gt; creates a new chat with bool attribute denoting if the chat is f2f or group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addNewUser&lt;/code&gt; -&amp;gt; add a new user to existing chats.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;removeUser&lt;/code&gt; – remove user from existing chat.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt; – gets the metadata of chat with an id.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delete&lt;/code&gt; – deletes the chat from CMH.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only complex method here is &lt;code&gt;addNewMessages&lt;/code&gt;. It increments the message counter within the chat and persists message content in MongoDB, for durability.&lt;/p&gt;

&lt;h3&gt;
  
  
  MongoDB
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Message Entity
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Message {
   @Id
   private String id;
   private int chatId;
   private String owner;
   private long offset;
   private Instant timestamp;
   private String content;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model for message content stored in a database there are three important fields here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chatId&lt;/code&gt; – represent chat in which particular message was send.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ownerId&lt;/code&gt; – the userId of message sender.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;offset&lt;/code&gt; – ordinal number of message within the chat, for retrieval ordering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  MessageStore
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface MessagesStore extends ReactiveMongoRepository&amp;lt;Message, String&amp;gt; {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing special, classic Spring Repository but in reactive fashion, provides the same set of features as &lt;code&gt;JpaRepository&lt;/code&gt;. It is used directly in ChatStore.&lt;/p&gt;

&lt;p&gt;Additionally in the main application class, &lt;code&gt;WebsocketsChatApplication&lt;/code&gt;, I am activating reactive repositories by using &lt;code&gt;@EnableReactiveMongoRepositories&lt;/code&gt;. Without this annotation &lt;code&gt;messageStore&lt;/code&gt;from above would not work.&lt;/p&gt;

&lt;p&gt;And here we go, we have the whole chat implemented. Let’s test it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Boot WebSocket – Testing
&lt;/h2&gt;

&lt;p&gt;For tests, I’m using &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; and &lt;a href="https://chromewebstore.google.com/detail/simple-websocket-client" rel="noopener noreferrer"&gt;Simple Web Socket Client&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I’m creating a new chat using Postman. In response body, I got a WebSocket URL to the recently created chat. &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd95f0gwptyh53gze1x6d.webp" alt="Spring Boot WebSocket - Chat Created" width="800" height="488"&gt;
&lt;/li&gt;
&lt;li&gt;Now it is time to use them and check if users can communicate with one another. Simple Web Socket Client comes into play here. Thus, I am connecting to the newly created chat here. &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu6hv9ohto3qhfank0j3.webp" alt="Spring Boot WebSocket - Chat connected" width="800" height="353"&gt;
&lt;/li&gt;
&lt;li&gt;Here we are, everything is working and users can communicate with each other. &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrl3kve4rgl12topwexs.webp" alt="First Message Sent" width="800" height="353"&gt; &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx3s028xuhdqctdhcj87.webp" alt="First Message Received" width="800" height="353"&gt; &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fluh62yb4in8wd0kp4gia.webp" alt="Spring Boot WebSocket - Second Message Received" width="800" height="353"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is one last thing to do. Let’s spend a moment to look at things that can be done better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can Be Done Better
&lt;/h2&gt;

&lt;p&gt;As what I have just built is the most basic chat app, there are a few (or in fact quite a lot) things that may be done better. Below, I listed the things I find worthy of improving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and rejoining support&lt;/strong&gt; — right now, everything is based on sessionId. It is not an optimal approach, It would be better to have some authentication in place and actual rejoining based on user data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sending attachments&lt;/strong&gt; — for now, the chat only supports simple text messages. While texting is the basic function of a chat, users enjoy exchanging images and audio files, too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; — there are no tests for now, but why leave it like this? Tests are always a good idea.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overflow in offset&lt;/strong&gt; — currency it is the simple int if we would track the offset for a very long time it will overflow sooner or later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Et voilà! The Spring Boot WebSocket chat is implemented, and the main task is done. You have some ideas on what to develop in next steps.&lt;/p&gt;

&lt;p&gt;Please keep in mind that this chat case is very simple, and it will require lots of changes and development for any type of commercial project.&lt;/p&gt;

&lt;p&gt;Anyway, I hope that you learned something new while reading this article.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Might interest you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/lock-free-programming/" rel="noopener noreferrer"&gt;Lock Free Programming in Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/ways-to-build-api/" rel="noopener noreferrer"&gt;7 Ways To Build API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/spring-boot-websocket/" rel="noopener noreferrer"&gt;Chatting with Spring &amp;amp; WebSocket&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>websockets</category>
    </item>
    <item>
      <title>Availability – Theory, Problems, Tools and Best Practices</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Wed, 09 Jul 2025 14:59:58 +0000</pubDate>
      <link>https://dev.to/pasksoftware/availability-theory-problems-tools-and-best-practices-d1f</link>
      <guid>https://dev.to/pasksoftware/availability-theory-problems-tools-and-best-practices-d1f</guid>
      <description>&lt;p&gt;Availability is the measure of a system’s ability to stay up and running despite the failures, of its parts. Today, I will explore this core trait of &lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;distributed systems&lt;/a&gt;. I will cover theory, challenges, tools and best practises to ensure your system stays up and running against all odds.&lt;/p&gt;

&lt;p&gt;Let’s start with theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is Availability?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Availability describes how our systems handle failures and determines the system’s uptime. Usually, we describe the availability of a system in “nines” notation. 99% availability guarantees a maximum of 14.40 minutes of downtime per day, while 99.999%-the so-called 5 nines-reduces this time to 846 milliseconds.&lt;/p&gt;

&lt;p&gt;Most cloud services have an SLA with either three (99.9%) to five (99.999%) nines availability guarantees for end users.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Availability (%)&lt;/th&gt;
&lt;th&gt;Downtime per day (~)&lt;/th&gt;
&lt;th&gt;Downtime per month (~)&lt;/th&gt;
&lt;th&gt;Downtime per year (~)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;144 minutes (2.4 hours)&lt;/td&gt;
&lt;td&gt;73 hours&lt;/td&gt;
&lt;td&gt;36.53 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;14 minutes&lt;/td&gt;
&lt;td&gt;7 hours&lt;/td&gt;
&lt;td&gt;3.65 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9&lt;/td&gt;
&lt;td&gt;1.5 minutes&lt;/td&gt;
&lt;td&gt;44 minutes&lt;/td&gt;
&lt;td&gt;8.77 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.99&lt;/td&gt;
&lt;td&gt;9 seconds&lt;/td&gt;
&lt;td&gt;4.4 minutes&lt;/td&gt;
&lt;td&gt;52.6 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.999&lt;/td&gt;
&lt;td&gt;846 milliseconds&lt;/td&gt;
&lt;td&gt;26 seconds&lt;/td&gt;
&lt;td&gt;5.3 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9999&lt;/td&gt;
&lt;td&gt;86.40 milliseconds&lt;/td&gt;
&lt;td&gt;2.6 seconds&lt;/td&gt;
&lt;td&gt;31.5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Additionally, the term &lt;em&gt;high availability&lt;/em&gt; or &lt;em&gt;HA&lt;/em&gt; is used to describe services that have at least 3 nines of availability guarantees.&lt;/p&gt;

&lt;p&gt;There is a famous struggle related to availability and consistency. The common notion is that in case of a failure, we can have either one or the other. While in most cases this is true, the topic as a whole is vastly more nuanced and complex. For example, &lt;a href="https://pasksoftware.com/crdts/" rel="noopener noreferrer"&gt;CRDTs&lt;/a&gt; put this whole statement into question; the same is true for Google’s internal Spanner.&lt;/p&gt;

&lt;p&gt;Moreover, we can use various techniques to balance both of these traits. A system may favor one over the other in certain places while not in others. Just remember: this struggle exists and is one of the most important cases of study in distributed systems research.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How To Measure Availability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Availability is probably the simplest trait to measure – at least for a single service. You probably already have uptime or downtime metrics in one of your dashboards. Just divide the value you have there by: 24 (hours), 1440 (minutes), 5184000 (seconds). Et voilà, you have your service daily uptime percentage ready, and you can easily see how many nines you archived.&lt;/p&gt;

&lt;p&gt;Things are getting more complicated when our service has multiple dependencies, or when we want to measure availability on the scale of whole system.&lt;/p&gt;

&lt;p&gt;As an example consider the service A with two dependencies: DB and Email Service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service A has uptime of 99.99%.&lt;/li&gt;
&lt;li&gt;DB has uptime of 99.9%.&lt;/li&gt;
&lt;li&gt;Email Service has uptime of 99%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, availability of service A is not 99.99 but in fact 98.89%.&lt;br&gt;&lt;br&gt;
0.9999 × 0.999 × 0.99 = 0.9889 =&amp;gt; 98.89%.&lt;/p&gt;

&lt;p&gt;In more readable format:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;SLA (nines)&lt;/th&gt;
&lt;th&gt;Availability (decimal)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Front-end API&lt;/td&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;td&gt;0.9999&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;0.9990&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email service&lt;/td&gt;
&lt;td&gt;99 %&lt;/td&gt;
&lt;td&gt;0.9900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Composite A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9999 × 0.9990 × 0.9900 = 0.9889 → 98.89%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.9889&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While the final difference is not big, it clearly illustrates the point. Availability of service is not a standalone but a product of all dependencies.&lt;/p&gt;

&lt;p&gt;The same principle applies to system. Availability of a system as a whole is a product of all its services and tools. Even a single poorly available component can bring whole system down.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Weakest link&lt;/th&gt;
&lt;th&gt;Best product you can ever reach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;99 % (two nines)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;&amp;lt; 99 %&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9 % (three nines)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;&amp;lt; 99.8 %&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.99 % (four nines)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;&amp;lt; 99.96 %&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a quick note on how you can structure your Availability related metrics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Example in an availability context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;(Indicator)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;http_request_success_ratio&lt;/code&gt; = successful requests ÷ total requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;(Objective)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http_request_success_ratio ≥ 99.95 % over 30 days&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;(Agreement)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;“We guarantee 99.9 % monthly availability; otherwise you get service credits.”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Signs That System Has Poor Availability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are a couple of behaviors we can notice, which indicate availability problems of our service. Additionally, some of those are similar to the signs of poor &lt;a href="https://pasksoftware.com/scalability/" rel="noopener noreferrer"&gt;scalability&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low uptime percentage&lt;/strong&gt; – most obvious of all, directly shows that the service is down and users cannot access it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service “flapping”&lt;/strong&gt; – the service oscillates between up and down as automated restarts or failovers repeatedly flip the service in and out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health-check Failures&lt;/strong&gt; – Persistent probe timeouts under normal load mean the service is down or will be down in near future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Mean Time To Recover&lt;/strong&gt; – outages last hours, before the team can resolve it and bring system back online.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suddenly traffic drops to zero&lt;/strong&gt; – service is either down or users gave up attempts to connect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct Feedback&lt;/strong&gt; – an important client is calling CTO/CIO (or whoever else) complaining everything is down, alerts start spinning, and other interesting events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Availability Game Changers&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In my opinion, the game change for availability is &lt;strong&gt;automatic and graceful failover&lt;/strong&gt;. While it sounds simple, it is actually more complex. To achieve it, we need to combine multiple different concepts and make the work together. Nonetheless, it is crucial for providing a zero downtime experience.&lt;/p&gt;

&lt;p&gt;The anatomy of state of the art zero-down-time failover:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Typical target time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Detect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Health probe sees anomalies (5× timeouts/60 s).&lt;/td&gt;
&lt;td&gt;≤ 5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Decide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orchestrator marks node &lt;em&gt;unhealthy&lt;/em&gt;, stops scheduling it.&lt;/td&gt;
&lt;td&gt;≤ 1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Redirect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Load balancer removes endpoint from pool; sticky sessions migrate.&lt;/td&gt;
&lt;td&gt;≤ 2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Restore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Replacement pod/VM starts and passes readiness checks.&lt;/td&gt;
&lt;td&gt;≤ 40 s (hot standby: ≈ 0 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of course, automatic failover is not a silver bullet and comes with drawbacks. The two most significant ones are higher complexity of the design and increased costs. Redundancy is responsible for increased costs, while failover itself adds the complexity.&lt;/p&gt;

&lt;p&gt;It may sound bad, unfortunately without such mechanism we will not be able to provide high availability.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tools For Availability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I have already covered automatic failover as key tools to build available systems. However, these are not the only concepts. There are more, and you can find them below.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Replication&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Replication is a method to implement redundancy. The key difference is that redundancy impacts all layers of our system from software to hardware. While replication is mostly related to the data layer.&lt;/p&gt;

&lt;p&gt;We provide multiple up-to-date copies of the same data-set, usually split across multiple nodes. Thus, in case one of the nodes fails the data is still available for the user.&lt;/p&gt;

&lt;p&gt;There are two main types of Replication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-master/Single-leader – only one of replica nodes is handling incoming writes – the leader. The rest of nodes provides read access and can be used to offload part of incoming traffic. Leader propagate changes to others nodes, usually, using some type of Write Ahead Log (WAL). If leader node fails or becomes unavailable for some reason. The leader election process takes place, and the new leader is selected from up-and-running nodes.&lt;/li&gt;
&lt;li&gt;Multi-master/multi-leader – all the nodes accept both reads and writes at the same time. Writes are then propagated to other nodes. The biggest problem in case is that the same write operation can end up on two different nodes at the same time. Thus, it requires separate conflict resolution mechanism.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The concept of replication is a very extensive one. The good walkthrough and comparison even of these two approaches is out of scope of this article. However, I promise to dive deeper into replication in separate article.&lt;/p&gt;

&lt;p&gt;For now remember following table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Single-master&lt;/th&gt;
&lt;th&gt;Multi-master&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Only one node accepts write&lt;/td&gt;
&lt;td&gt;Multiple nodes accepts write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Propagate via WAL&lt;/td&gt;
&lt;td&gt;Conflict resolution and propagation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Automatic Failover&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Automatic and gracefully (not noticeable by user) failover mechanism is the key for availability.&lt;/p&gt;

&lt;p&gt;Good automatic failover we will need to combine at least three concepts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redundancy – we need more than one node to even start thinking of building any failover.&lt;/li&gt;
&lt;li&gt;Health checks – we need properly defined health checks to detect if nodes are down or should not handle user requester.&lt;/li&gt;
&lt;li&gt;Load-balancer/actual failover – we need way to change the failing components and redirect the traffic to up-and-running ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each piece alone is insufficient; all must work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Isolating failure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Another way to increase the availability of our system is to &lt;strong&gt;isolate failures&lt;/strong&gt;. By doing so, we can ensure that a failure of one component will not cause the cascade failure of the other components involved in the same processing flow.&lt;/p&gt;

&lt;p&gt;As with most concepts from this paragraph there is no single tool or method to achieve that. Instead, we can follow one of the patterns below. We can also mix different patterns.&lt;/p&gt;

&lt;p&gt;Let’s dive into them below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breaker&lt;/strong&gt; – one of the most common microservices patterns in existences. It implements the &lt;strong&gt;fail-fast&lt;/strong&gt; concepts in a way similar to an electrical circuit breaker. If multiple consecutive calls to other service fail in certain period the circuit breaks switches. Then for the duration of a timeout period all attempts to invoke that service will fail immediately. Thus reducing the load on possibly faulty service and giving it time to recover. Also avoids introducing potential timeouts on other stages of the flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulkhead&lt;/strong&gt; – according to this pattern components and resources in our system should be compartmentalized. Partitioning should be done in such a way that components do not share any resources. For example, each partition should have its own thread pools, connections pools and CPU or memory limits. Such split will decrease the chances of one component overuse (high resource utilization) impact the other components in the system. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Kernel&lt;/strong&gt; – we split our system into two types of components core and side ones. The core ones must not fail whatever the reason. The side ones may fail, and we should be able to easily restart them. Then we can move the side ones into the “outskirts” of the system. Thus, we end with reliable core and easy to restart leaf components.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Multi-Region or Multi-Cloud Deployment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Multi-Availability Zone or Multi-Region Deployment will protect us form the least expected type of failures. The ones that will wipe-out whole datacenter or multiple data-centers located in a particular region. Like burning of &lt;a href="https://journal.uptimeinstitute.com/learning-from-the-ovhcloud-data-center-fire/" rel="noopener noreferrer"&gt;OVH datacenter in France&lt;/a&gt; or &lt;a href="https://www.crn.com/news/cloud/google-data-center-explosion-causes-injuries-outages" rel="noopener noreferrer"&gt;GCP electrical problem in Iowa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can go even further and build &lt;strong&gt;Multi-Cloud failover&lt;/strong&gt;. If your core cloud provider is down you can switch to a backup. While it adds a ton of extra complexity to your system it drastically reduces the probability of system-wide failure even more. Region wide failures are rare by themselves. Provider wide failures are even rarer. Nevertheless, both may happen. Being able to handle them probably will not decide the difference between 99.99% and lower vitality tiers.&lt;/p&gt;

&lt;p&gt;However, being able to handle such events have a few advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Besides staying alive when others are down.&lt;/li&gt;
&lt;li&gt;Indicate how good your architecture is.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Chaos Engineering/ Fault Injection
&lt;/h3&gt;

&lt;p&gt;Chaos engineering will not actually help you to build available system by itself. Rather, it helps you ensure that your system is in fact available. By introducing deliberate and trackable failure you can identify weaknesses and problems that will not show up in any other case. I also mentioned this concept &lt;a href="https://pasksoftware.com/test-pyramid-best-practices/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just remember it is not fully safe and double-check that your system will be able to handle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why We Fail To Achieve High Availability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After what, how, and why, it is time for why we fail. In my opinion and experience, there are a few factors that lead to our failure in building available systems.&lt;/p&gt;

&lt;p&gt;Some reason will be the same as in the case of my article on &lt;a href="https://pasksoftware.com/scalability/" rel="noopener noreferrer"&gt;scalability.&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring the &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;trade-offs&lt;/a&gt;&lt;/strong&gt; – every decision we make has short- and long-lasting consequences we have to be aware of. Of course, we can ignore them; still, we have to know them first and be conscious as to why we are ignoring some potential drawbacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incorrect health-checks&lt;/strong&gt; – they react either too slowly or too quickly. Restarting service too early or too late increasing the likelihood of users experiencing the failure. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Redundancy&lt;/strong&gt; – critical components do not have properly configured redundancy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Badly designed Failover&lt;/strong&gt; – we are unable to redirect the traffic to the up-and-running nodes fast enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below a simple checklist how to increase the chance of not failing in availability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Do &lt;strong&gt;today&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Add health check&lt;/strong&gt; to every component.&lt;/td&gt;
&lt;td&gt;30 min work slashes 502 errors during deploys/failovers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Track availability product&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Makes hidden single points painfully obvious.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Set a written SLO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aligns team on what “good enough” means.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Run a failover drill&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Check your design in practise.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I have shared a number of concepts and approaches for building highly available systems.&lt;/p&gt;

&lt;p&gt;Let’s do a quick recap of key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Making highly available systems requires mixing different concepts like: redundancy, healthcheck and failovers.&lt;/li&gt;
&lt;li&gt;Proper health checks will help you keep up with the state of your components.&lt;/li&gt;
&lt;li&gt;Isolating failures and preventing their propagation will keep the system running even if some components will fail.&lt;/li&gt;
&lt;li&gt;Multi-region deployment will save you in the most unexpected moment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some concepts discussed here can’t be implemented using a single tool. They require architectural thinking and coordination across layers of the stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually part of database product you are using&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automatic failover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;K8s probes, Cloud autoscaling products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Resilience4j, K8s Namespaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi AZ&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud providers Availability Zones&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 High availability isn't just a metric - it is a mindset. Build for failure. Monitor everything. And treat availability as first class feature.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wish you luck on your struggle with availability.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/what-is-availability/" rel="noopener noreferrer"&gt;Availability – Theory, Problems, Tools and Best Practices&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>softwareengineering</category>
      <category>availability</category>
    </item>
    <item>
      <title>From REST To Message Queue – 7 Ways To Build APIs</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Tue, 17 Jun 2025 15:19:15 +0000</pubDate>
      <link>https://dev.to/pasksoftware/from-rest-to-message-queue-7-ways-to-build-apis-1ojh</link>
      <guid>https://dev.to/pasksoftware/from-rest-to-message-queue-7-ways-to-build-apis-1ojh</guid>
      <description>&lt;p&gt;There are multiples ways to build API. I have already mentioned and describe some of the in different articles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/grpc-vs-rest/" rel="noopener noreferrer"&gt;gRPC vs REST&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/websockets-vs-sse/" rel="noopener noreferrer"&gt;WebSockets vs SSE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This text is a kind of One Ring article — one to rule them all. I want you to have a single place where you can find a comparison of all the approaches done in clear and consistent manner. Thus, I have put here all the previous comparisons, and add some more into this text.&lt;/p&gt;

&lt;p&gt;I will compare a total of 7 ways across 10 axes.&lt;/p&gt;

&lt;p&gt;Tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;REST&lt;/li&gt;
&lt;li&gt;gRPC&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;SSE&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Webhooks&lt;/li&gt;
&lt;li&gt;Message-Queue Base&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Axes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Communication Direction&lt;/li&gt;
&lt;li&gt;Underlying protocols&lt;/li&gt;
&lt;li&gt;Message Structure&lt;/li&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Data size&lt;/li&gt;
&lt;li&gt;Throughput&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Ease of adoption&lt;/li&gt;
&lt;li&gt;Tooling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s start today’s journey from REST. As it is probably the most common way of building APIs I would use it as a baseline for all the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Ways To Build API&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. REST&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;REST, or Representational State Transfer, is an architecture styles. It uses HTTP as the underlying communication medium thus it is stateless by nature and can benefit from all the advantages of HTTP, like caching. It can utilize both HTTP/1.1, HTTP/2.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt;: you need a simple, cache-friendly, web-native API consumed by every language or tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt;: you need full-duplex streams or extremely low latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuanttvi50gbiihj9ixxt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuanttvi50gbiihj9ixxt.webp" alt="Ways To Build API - REST" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. gRPC&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;gRPC is probably the most modern implementation of the relatively old concept of — Remote Procedure Call. gRPC usesGoogle’s Protocol Buffers as a serialization tool. By default, it utilizes HTTP/2 as transport medium data and exchange data in a binary format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt;: services need compact binary messages, strong typing, and bidirectional streaming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt;: you must expose an API directly to browsers without extra tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxaoujdfr5gu629y0xp6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxaoujdfr5gu629y0xp6.webp" alt="Ways To Build API - gRPC" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. WebSockets&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;WebSocket provides bidirectional communication between a server and client with the usage of a single long-lasting TCP connection. Thanks to this feature, the data is exchanged between interested parties in “real-time”. Each message is send as binary frame data or Unicode text. While WebSockets utilize custom protocol for most of the time it is still using HTTP for initial handshake.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shine when&lt;/strong&gt;: both client and server need real-time, low-latency push in either direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggle when&lt;/strong&gt;: intermediaries (CDNs, firewalls) or strict request-response patterns dominate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctwkfxxxkyo88mw05cwh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctwkfxxxkyo88mw05cwh.webp" alt="Ways To Build API - WebSockets" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. SSE&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;SSE is a technology that allows a web server to send updates to a web page. It is a part of HTML 5 specification and utilizes a single long live HTTP connection to send data in “real-time”. It can use both HTTP/1.1 and HTTP/2. SSE also has its unique MIME type: &lt;strong&gt;text/event-stream&lt;/strong&gt;. The important thing here is that it is can be only used for server-browser communication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt;: the server pushes one-way event streams to browsers with a minimal setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt;: you need client-to-server push or non-browser consumers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t9rpm4rzt2qugjql1ns.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t9rpm4rzt2qugjql1ns.webp" alt="SSE" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. GraphQL&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;GraphQL is a query language for your API. It allows clients to request only the subset of data they need. The client knows the server’s endpoint, the endpoint provides a schema. The schema defines the communication protocol, inputs and outputs. The request, is validated with the schema, thus malformed request woulds be rejected by the server with a proper error message.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt;: clients must trim over-fetching and compose rich queries from one endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt;: the workload is write-heavy or teams cannot maintain a strict schema discipline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn3o3s5yu0g9fvpk9z30.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn3o3s5yu0g9fvpk9z30.webp" alt="GraphQL" width="576" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Webhooks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Webhooks address the question — Has anything changed yet?. It is a push-style HTTP callback that lets another service tell your app when something relevant happens. It works in, in “real” time, no polling, no sockets, just an outbound POST.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt;: you want lightweight server-to-server notifications without polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt;: delivery guarantees must be exactly-once, or the receiver is offline for long periods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztu2rn3l7hip5t40r7tr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztu2rn3l7hip5t40r7tr.webp" alt="Webhook" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Message-Queue&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The message queue base approach uses a middleware as a way to communicate between services. Usually in form of some platform like &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; or &lt;a href="https://www.rabbitmq.com/" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt;. The main idea behind message queues is to provide asynchronous communication. Messages are sent to a queue, which acts as a buffer between the sender and receiver. This decouples the sender and receiver and allows them to operate independently of each other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shine when&lt;/strong&gt;: you need high-throughput, decoupled, async processing with a replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggle when&lt;/strong&gt;: you require simple, stateless request-response or openly exposed public APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwol1p7i9h23q1cdzfgak.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwol1p7i9h23q1cdzfgak.webp" alt="Ways To Build API - MQ" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Ways To Build API – Comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Below is the most important takeaway from this article. For traits scaled from Very Low to Very High I am using REST a baseline. Thus, for example if one the complexity of the tools is described as Very High you can think of it as far more complex then REST.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Communication Direction&lt;/th&gt;
&lt;th&gt;Underlying Protocols&lt;/th&gt;
&lt;th&gt;Message Structure&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Security²&lt;/th&gt;
&lt;th&gt;Data Size&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Ease of Adoption&lt;/th&gt;
&lt;th&gt;Tooling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REST&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1 or HTTP/2&lt;/td&gt;
&lt;td&gt;Mostly JSON or XML (text)&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Extensive (Postman, Swagger/OpenAPI, cURL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Unidirectional or Bi-directional&lt;/td&gt;
&lt;td&gt;HTTP/2&lt;/td&gt;
&lt;td&gt;Protocol Buffers (binary)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;mTLS&lt;/td&gt;
&lt;td&gt;Very Small – binary&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Moderate but growing&lt;/td&gt;
&lt;td&gt;Decent (gRPCurl, Postman)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSockets&lt;/td&gt;
&lt;td&gt;Bi-directional&lt;/td&gt;
&lt;td&gt;WebSocket (upgrade from HTTP/1.1 or HTTP/2)&lt;/td&gt;
&lt;td&gt;Text or binary frames&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;WSS&lt;/td&gt;
&lt;td&gt;Small – binary frames&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE (Server-Sent Events)&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;text/event-stream&lt;/code&gt; (UTF-8)&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Moderate – plain text&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL&lt;/td&gt;
&lt;td&gt;Unidirectional, Bi-directional with subscriptions&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2; WebSocket for subscriptions&lt;/td&gt;
&lt;td&gt;JSON (text)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Variable; fewer round-trips&lt;/td&gt;
&lt;td&gt;Medium–high&lt;/td&gt;
&lt;td&gt;Moderate but rapidly growing&lt;/td&gt;
&lt;td&gt;Very Good (Apollo, GraphiQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1 or HTTP/2&lt;/td&gt;
&lt;td&gt;JSON, form-encoded, or custom&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Depends on event volume (bursty)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Good (ngrok, request-bin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message-Queue&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;Kafka TCP, AMQP, MQTT, NATS, …&lt;/td&gt;
&lt;td&gt;Binary records, Avro, JSON …&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;ACLs, TLS&lt;/td&gt;
&lt;td&gt;Small to medium – highly tunable&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are no good or bad here, nor any silver bullets, it just a couple of tools you can use.&lt;/p&gt;

&lt;p&gt;Nonetheless, if you want to know my recommendations here they are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario / Tech&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;th&gt;WebSockets&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;GraphQL&lt;/th&gt;
&lt;th&gt;Webhooks&lt;/th&gt;
&lt;th&gt;Message Queue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile → backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IoT / edge device&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service-To-Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser push / live UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3rd integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming pipelines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Remember, if you will be building anything more complex then a very focus microservice the odds are that you will be using more than one approach. &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;Mixing different&lt;/a&gt; tools the get the best design is part of our daily struggle just please do not overengineer your solution.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/ways-to-build-api/" rel="noopener noreferrer"&gt;From REST To Message Queue – 7 Ways To Build APIs&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>rest</category>
      <category>grpc</category>
    </item>
    <item>
      <title>7 API Integration Patterns: REST, gRPC, SSE, WS &amp; Queues</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Mon, 16 Jun 2025 18:19:15 +0000</pubDate>
      <link>https://dev.to/pasksoftware/7-api-integration-patterns-rest-grpc-sse-ws-queues-206o</link>
      <guid>https://dev.to/pasksoftware/7-api-integration-patterns-rest-grpc-sse-ws-queues-206o</guid>
      <description>&lt;p&gt;There are multiples API integration patterns. I have already mentioned and describe some of the in different articles: &lt;a href="https://pasksoftware.com/grpc-vs-rest/" rel="noopener noreferrer"&gt;gRPC vs REST&lt;/a&gt;, &lt;a href="https://pasksoftware.com/websockets-vs-sse/" rel="noopener noreferrer"&gt;WebSockets vs SSE&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This text is a kind of One Ring article — one to rule them all. I want you to have a single place where you can find a comparison of all the approaches done in clear and consistent manner. Thus, I have put here all the previous comparisons, and add some more into this text.&lt;/p&gt;

&lt;p&gt;I will compare a total of 7 API integration patterns 10 axes.&lt;/p&gt;

&lt;p&gt;Tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;REST&lt;/li&gt;
&lt;li&gt;gRPC&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;SSE&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Webhooks&lt;/li&gt;
&lt;li&gt;Message-Queue Base&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Axes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Communication Direction&lt;/li&gt;
&lt;li&gt;Underlying protocols&lt;/li&gt;
&lt;li&gt;Message Structure&lt;/li&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Data size&lt;/li&gt;
&lt;li&gt;Throughput&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Ease of adoption&lt;/li&gt;
&lt;li&gt;Tooling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s start today’s journey from REST. As it is probably the most common way of building APIs I would use it as a baseline for all the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;API Integration Patterns&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. REST&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;REST, or Representational State Transfer, is an architecture styles. It uses HTTP as the underlying communication medium thus it is stateless by nature and can benefit from all the advantages of HTTP, like caching. It can utilize both HTTP/1.1, HTTP/2.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt; : you need a simple, cache-friendly, web-native API consumed by every language or tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt; : you need full-duplex streams or extremely low latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuanttvi50gbiihj9ixxt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuanttvi50gbiihj9ixxt.webp" alt="API Integration Patterns - REST" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. gRPC&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;gRPC is probably the most modern implementation of the relatively old concept of — Remote Procedure Call. gRPC usesGoogle’s Protocol Buffers as a serialization tool. By default, it utilizes HTTP/2 as transport medium data and exchange data in a binary format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt; : services need compact binary messages, strong typing, and bidirectional streaming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt; : you must expose an API directly to browsers without extra tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxaoujdfr5gu629y0xp6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxaoujdfr5gu629y0xp6.webp" alt="API Integration Patterns - gRPC" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. WebSockets&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;WebSockets provides a &lt;a href="https://pasksoftware.com/spring-boot-websocket/" rel="noopener noreferrer"&gt;real-time&lt;/a&gt; bidirectional communication between a server and client with the usage of a single long-lasting TCP connection.&lt;/p&gt;

&lt;p&gt;Thanks to this feature, the data is exchanged between interested parties in “real-time”. Each message is send as binary frame data or Unicode text. While WebSockets utilize custom protocol for most of the time it is still using HTTP for initial handshake.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shine when&lt;/strong&gt; : both client and server need real-time, low-latency push in either direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggle when&lt;/strong&gt; : intermediaries (CDNs, firewalls) or strict request-response patterns dominate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctwkfxxxkyo88mw05cwh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctwkfxxxkyo88mw05cwh.webp" alt="API Integration Patterns - WebSockets" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. SSE&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;SSE is a technology that allows a web server to send updates to a web page. It is a part of HTML 5 specification and utilizes a single long live HTTP connection to send data in “real-time”. It can use both HTTP/1.1 and HTTP/2. SSE also have its unique MIME type:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;text/event-stream&lt;/strong&gt;. The important thing here is that it is can be only used for server-browser communication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt; : the server pushes one-way event streams to browsers with a minimal setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt; : you need client-to-server push or non-browser consumers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t9rpm4rzt2qugjql1ns.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t9rpm4rzt2qugjql1ns.webp" alt="SSE" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. GraphQL&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;GraphQL is a query language for your API. It allows clients to request only the subset of data they need. The client knows the server’s endpoint, the endpoint provides a schema. The schema defines the communication protocol, inputs and outputs. The request, is validated with the schema, thus malformed request woulds be rejected by the server with a proper error message.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt; : clients must trim over-fetching and compose rich queries from one endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt; : the workload is write-heavy or teams cannot maintain a strict schema discipline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn3o3s5yu0g9fvpk9z30.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn3o3s5yu0g9fvpk9z30.webp" alt="GraphQL" width="576" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Webhooks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Webhooks address the question — Has anything changed yet?. It is a push-style HTTP callback that lets another service tell your app when something relevant happens. It works in, in “real” time, no polling, no sockets, just an outbound POST.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shines when&lt;/strong&gt; : you want lightweight server-to-server notifications without polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggles when&lt;/strong&gt; : delivery guarantees must be exactly-once, or the receiver is offline for long periods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztu2rn3l7hip5t40r7tr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztu2rn3l7hip5t40r7tr.webp" alt="Webhook" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Message-Queue&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The message queue base approach uses a middleware as a way to communicate between services. Usually in form of some platform like &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; or &lt;a href="https://www.rabbitmq.com/" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt;. The main idea behind message queues is to provide asynchronous communication. Messages are sent to a queue, which acts as a buffer between the sender and receiver. This decouples the sender and receiver and allows them to operate independently of each other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shine when&lt;/strong&gt; : you need high-throughput, decoupled, async processing with a replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struggle when&lt;/strong&gt; : you require simple, stateless request-response or openly exposed public APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwol1p7i9h23q1cdzfgak.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwol1p7i9h23q1cdzfgak.webp" alt="API Integration Patterns - MQ" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;API Integration Patterns – Comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Below is the most important takeaway from this article. For traits scaled from Very Low to Very High I am using REST a baseline. Thus, for example if one the complexity of the tools is described as Very High you can think of it as far more complex then REST.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Communication Direction&lt;/th&gt;
&lt;th&gt;Underlying Protocols&lt;/th&gt;
&lt;th&gt;Message Structure&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Security²&lt;/th&gt;
&lt;th&gt;Data Size&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Ease of Adoption&lt;/th&gt;
&lt;th&gt;Tooling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REST&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1 or HTTP/2&lt;/td&gt;
&lt;td&gt;Mostly JSON or XML (text)&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Extensive (Postman, Swagger/OpenAPI, cURL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Unidirectional or Bi-directional&lt;/td&gt;
&lt;td&gt;HTTP/2&lt;/td&gt;
&lt;td&gt;Protocol Buffers (binary)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;mTLS&lt;/td&gt;
&lt;td&gt;Very Small – binary&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Moderate but growing&lt;/td&gt;
&lt;td&gt;Decent (gRPCurl, Postman)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSockets&lt;/td&gt;
&lt;td&gt;Bi-directional&lt;/td&gt;
&lt;td&gt;WebSocket (upgrade from HTTP/1.1 or HTTP/2)&lt;/td&gt;
&lt;td&gt;Text or binary frames&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;WSS&lt;/td&gt;
&lt;td&gt;Small – binary frames&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE (Server-Sent Events)&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;text/event-stream&lt;/code&gt; (UTF-8)&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Moderate – plain text&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL&lt;/td&gt;
&lt;td&gt;Unidirectional, Bi-directional with subscriptions&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2; WebSocket for subscriptions&lt;/td&gt;
&lt;td&gt;JSON (text)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Variable; fewer round-trips&lt;/td&gt;
&lt;td&gt;Medium–high&lt;/td&gt;
&lt;td&gt;Moderate but rapidly growing&lt;/td&gt;
&lt;td&gt;Very Good (Apollo, GraphiQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;HTTP/1.1 or HTTP/2&lt;/td&gt;
&lt;td&gt;JSON, form-encoded, or custom&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Depends on event volume (bursty)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Good (ngrok, request-bin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message-Queue&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;Kafka TCP, AMQP, MQTT, NATS, …&lt;/td&gt;
&lt;td&gt;Binary records, Avro, JSON …&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;ACLs, TLS&lt;/td&gt;
&lt;td&gt;Small to medium – highly tunable&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are no good or bad here, nor any silver bullets, it just a couple of tools you can use.&lt;/p&gt;

&lt;p&gt;Nonetheless, if you want to know my recommendations here they are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario / Tech&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;th&gt;WebSockets&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;GraphQL&lt;/th&gt;
&lt;th&gt;Webhooks&lt;/th&gt;
&lt;th&gt;Message Queue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile → backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IoT / edge device&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service-To-Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser push / live UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3rd integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming pipelines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Remember, if you will be building anything more complex then a very focus microservice the odds are that you will be using more than one approach. &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;Mixing different&lt;/a&gt; tools the get the best design is part of our daily struggle just please do not overengineer your solution.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;May also interest you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/archunit/" rel="noopener noreferrer"&gt;ArchUnit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;Distributed Systems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/api-integration-patterns/" rel="noopener noreferrer"&gt;7 API Integration Patterns: REST, gRPC, SSE, WS &amp;amp; Queues&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>ArchUnit Guide – How to Unit Test Your Architecture</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Mon, 16 Jun 2025 14:57:31 +0000</pubDate>
      <link>https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao</link>
      <guid>https://dev.to/pasksoftware/archunit-guide-how-to-unit-test-your-architecture-57ao</guid>
      <description>&lt;p&gt;Enforcing a specific package structure or architecture is very important. Especially in Java where some things must be public to work correctly or actually be available outside its package. ArchUnit is an open-source library that will help you whenever the compiler is not enough.&lt;/p&gt;




&lt;p&gt;All the code examples from this article is available in my &lt;a href="https://github.com/Pask423/articles-misc/tree/master/archunit" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is ArchUnit?
&lt;/h2&gt;

&lt;p&gt;ArchUnit is an open-source library for writing and enforcing architecture rules within your project. There is no use of facades and encapsulations when you can just reach out and take what you want. It is even more significant when you are trying to follow ports and adapters or other approaches that impose very strict restrictions on which classes should use others and in what way.&lt;/p&gt;

&lt;p&gt;That is the moment where ArchUnit comes into play. It can easily check the dependencies between packages and classes — which class is using/importing the other. With this “simple” feature we can easily set up the set of rules that will put restrictions on how our classes can interact with one another. Later we can easily add these rules to our &lt;a href="https://pasksoftware.com/test-pyramid-best-practices/" rel="noopener noreferrer"&gt;test suite&lt;/a&gt; and by extension unit test our architecture.&lt;/p&gt;

&lt;p&gt;For all designs and purposes the rules are normal testes and can be easily run by any unit test library/framework. Beside “simple” packages and classes dependencies check mentioned above it can also check dependencies between layers and slices, check for cyclic dependencies and more.&lt;/p&gt;

&lt;p&gt;We can create following rules with the help of ArchUnit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Classes in package X should only depend on classes in package Y.”&lt;/li&gt;
&lt;li&gt;“Classes in the service layer should not access controller layer classes.” &lt;/li&gt;
&lt;li&gt;“No cyclic dependencies should exist among these packages.” &lt;/li&gt;
&lt;li&gt;Prevent a field and setter based injection &lt;/li&gt;
&lt;li&gt;Ensure @Transactional annotation is used only in the service layer &lt;/li&gt;
&lt;li&gt;Enforce @Repository and @Service annotation usage in specific packages &lt;/li&gt;
&lt;li&gt;….&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without going into much detail ArchUnit works by reading and analyzing bytecode not the source code itself. Thus, our rules are not applied to source code per se but rather to output bytecode.&lt;/p&gt;

&lt;h3&gt;
  
  
  ArchUnit-Junit
&lt;/h3&gt;

&lt;p&gt;ArchUnit-Junit artifact is part of the wider ArchUnit framework. It makes the tests more descriptive and smaller by removing a lot of JUnit related boilerplate code. The most import part of this package is &lt;code&gt;ArchTest&lt;/code&gt; annotation. With using it we can write tests as methods not JUnit tests. The artifact will take care of actually converting the method into proper JUnit test.&lt;/p&gt;

&lt;h4&gt;
  
  
  ArchUnit-Junit
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@ArchTest
static final ArchRule classesInXShouldOnlyDependOnClassesInY =
        ArchRuleDefinition
              .classes()
              .that().resideInAPackage("..x..")
              .should().onlyDependOnClassesThat()
              .resideInAnyPackage(
                        "..y..",
                        "java.."
                );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JUnit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testClassesInXShouldOnlyDependOnClassesInY() {
    ArchRule rule = classes()
          .that().resideInAPackage("..x..")
          .should().onlyDependOnClassesThat()
          .resideInAnyPackage(
                    "..y..",   
                    "java.."   
            );

    rule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is seem no a big deal, however I can see potential benefits if you have a lot of tests. Personally, I prefer the classic JUnit way.&lt;/p&gt;

&lt;p&gt;All the tests written here follow a JUnit way without the &lt;code&gt;archunit-junit5-engine&lt;/code&gt;. Nevertheless, you can find the examples written with &lt;code&gt;junit-archunit&lt;/code&gt; lib in &lt;a href="https://github.com/Pask423/articles-misc/tree/master/archunit/src/test/java/org/ps/archjunit" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ArchUnit Examples
&lt;/h2&gt;

&lt;p&gt;Let’s start with implementation of all the rules from above. Then I will move on to presenting rules that will ensure your ports &amp;amp; adapters setup remains unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classes in package X should only depend on classes in package Y.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testClassesInXShouldOnlyDependOnClassesInY() {
    // Given: Define a rule that restricts classes in package '..x..' 
    // to depend only on classes in '..y..' or standard Java packages.
    ArchRule rule = classes()
            .that().resideInAPackage("..x..")
            .should().onlyDependOnClassesThat()
            .resideInAnyPackage(
                    "..y..", // Allow dependency on package '..y..'
                    "java.." // Allow dependency on Java standard library
            );

    // Then
    rule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Classes in the service layer should not access controller layer classes.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testServiceLayerShouldNotAccessControllers() {
    // Given: Define a rule that prevents the service layer
    // from depending on classes in the controller layer.
    ArchRule rule = noClasses()
            .that().resideInAPackage("..service..")
            .should().dependOnClassesThat()
            .resideInAPackage("..controller..");

    // Then
    rule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No cyclic dependencies should exist among packages.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testNoCyclicDependencies() {
    // Given: Define a rule to ensure there are no cyclic dependencies
    // between modules grouped by their first-level sub-packages under 'org.ps'.
    ArchRule rule = SlicesRuleDefinition.slices()
            .matching("org.ps.(*)..") // Define slices by sub-packages under 'org.ps'
            .should()
            .beFreeOfCycles(); // Ensure there's no cyclic dependency between them
    // Then
    rule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prevent the field and setter based
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testNoFieldInjection() {
    // Given: Define a rule that disallows field injection using @Autowired.
    ArchRule noFieldInjectionRule = noFields()
            .should().beAnnotatedWith(Autowired.class)
            .because("Use constructor injection instead of field injection.");

    // Also define a rule that disallows setter injection using @Autowired.
    ArchRule noSetterInjectionRule = noMethods()
            .that().haveNameMatching("set[A-Z].*")
            .should().beAnnotatedWith(Autowired.class)
            .because("Use constructor injection instead of setter injection.");

    // When: Combine both rules into one composite rule.
    ArchRule compositeRule = CompositeArchRule.of(noFieldInjectionRule).and(noSetterInjectionRule);

    // Then
    compositeRule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ensure @Transactional annotation is used only in the service layer.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testTransactionalAnnotationOnlyInService() {
    // Given: Define a rule that ensures classes annotated with @Transactional
    // are located in the service layer.
    ArchRule classLevelTransactional = classes()
            .that().areAnnotatedWith(Transactional.class)
            .should().resideInAPackage("..service..")
            .because("Class-level @Transactional belongs in the service layer only.");

    // Also define a rule for methods annotated with @Transactional
    // to be declared only in service layer classes.
    ArchRule methodLevelTransactional = methods()
            .that().areAnnotatedWith(Transactional.class)
            .should().beDeclaredInClassesThat().resideInAPackage("..service..")
            .because("Method-level @Transactional belongs in the service layer only.");

    // When: Combine both rules into one composite rule.
    ArchRule compositeRule = CompositeArchRule.of(classLevelTransactional).and(methodLevelTransactional);

    // Then
    compositeRule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enforce @Repository and @Service annotation usage in specific packages.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testRepositoryAnnotationInRepositoryPackage() {
    // Given: Define a rule that ensures @Repository-annotated classes
    // are only located in the repository package.
    ArchRule rule = classes()
            .that().areAnnotatedWith(Repository.class)
            .should().resideInAPackage("..repository..");

    // Then
    rule.check(IMPORTED_CLASSES);
}

@Test
void testServiceAnnotationInServicePackage() {
    // Given: Define a rule that ensures @Service-annotated classes
    // are only located in the service package.
    ArchRule rule = classes()
            .that().areAnnotatedWith(Service.class)
            .should().resideInAPackage("..service..");

    // Then
    rule.check(IMPORTED_CLASSES);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ArchUnit &amp;amp; Hexagonal Architecture
&lt;/h2&gt;

&lt;p&gt;Here the setup is somewhat more complex. A complete set of ArchUnit tests for Hexagonal architecture.&lt;/p&gt;

&lt;p&gt;Due to Java packaging model, enforcing proper classes and methods visibility is sometimes impossible. Thus, someone may easily use the class outside its intended scope. By extend breaking the encapsulation and our beautiful separation of domain and infrastructure.&lt;/p&gt;

&lt;p&gt;Let’s consider following setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org.ps
├─ domain
│ └─ ... (domain models and services)
├─ application
│ ├─ port
│ │ ├─ in
│ │ │ └─ ... (interfaces for incoming incoming requests and messages)
│ │ └─ out
│ │ └─ ... (interfaces for outgoing requests and messages)
│ └─ ... (application services, use case implementations)
├─ adapters
│ ├─ in (incoming requests and messages)
│ └─ out (outgoing requests and messages)
├─ infrastructure
│ └─ ... (external setups - DB connections, queues, metrics)
└─ config
└─ ... (configurations classes for all other packages)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This is the closest to recommended package structure for hexagonal architecture I managed to get. It seems that there are no one general way. Almost every article is pushing its one version.&lt;/p&gt;




&lt;p&gt;We want our structure to obey following set of rules, as far as I have managed to understand industry-wide standard when it comes to hexagonal architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain may not access any layer but can be access by Application and Adapters layers.&lt;/li&gt;
&lt;li&gt;Application may access the Config and Domain layers but can be access by Adapters layer.&lt;/li&gt;
&lt;li&gt;Adapters may access Application, Adapters, Domain and Infrastructure by cannot be access by other layers.&lt;/li&gt;
&lt;li&gt;Infrastructure can only access Config layer but can be access only by Adapters.&lt;/li&gt;
&lt;li&gt;Config may not be access any layer but can be access by Application, Adapters, Domain and Infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is how we may test and enforce it with the help of ArchUnit. The test is quite lengthy but particular rules are clearly split from one another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
public void hexagonArchTest() {
    // Given
    JavaClasses importedClasses = new ClassFileImporter().importPackages("org.ps.hexagon");
    LayeredArchitecture portsAndAdaptersLayers = layeredArchitecture()
          .consideringOnlyDependenciesInLayers()
            // Define &amp;gt;each “layer” by its package
          .layer("Adapters").definedBy("..adapters..")
          .layer("Application").definedBy("..application..")
          .layer("Config").definedBy("..config..")
          .layer("Domain").definedBy("..domain..")
          .layer("Infrastructure").definedBy("..infrastructure..")
            // Domain may not access any layer but can be access by Application and Adapters layers.
          .whereLayer("Domain").mayNotAccessAnyLayer()
          .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Adapters")
            // Application may access the Config and Domain layers but can be access by Adapters layer.
          .whereLayer("Application").mayOnlyAccessLayers("Config", "Domain")
          .whereLayer("Application").mayOnlyBeAccessedByLayers("Adapters")
            // Adapters may access Application, Adapters, Domain and Infrastructure but cannot be access by other layers.
          .whereLayer("Adapters").mayOnlyAccessLayers("Infrastructure", "Config", "Application", "Domain")
          .whereLayer("Adapters").mayNotBeAccessedByAnyLayer()
            // Infrastructure can only access Config layer but can be access only by Adapters.
          .whereLayer("Infrastructure").mayOnlyAccessLayers("Config")
          .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Adapters")
            // Config may not be access any layer but can be access by Application, Adapters, Domain and Infrastructure.
          .whereLayer("Config").mayNotAccessAnyLayer()
          .whereLayer("Config").mayOnlyBeAccessedByLayers("Application", "Adapters", "Domain", "Infrastructure");

    // Then
    portsAndAdaptersLayers.check(importedClasses);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Here we are, that is all I wanted to share with you today. If you want some more examples you can find them either on &lt;a href="https://github.com/TNG/ArchUnit/tree/main/archunit-example" rel="noopener noreferrer"&gt;ArchUnit GitHub&lt;/a&gt; or in &lt;a href="https://www.archunit.org/use-cases" rel="noopener noreferrer"&gt;their docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the other hand, you can just start typing and see where the API will guide you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you for your time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/archunit/" rel="noopener noreferrer"&gt;ArchUnit Guide – How to Unit Test Your Architecture&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>softwareengineering</category>
      <category>archunit</category>
      <category>testing</category>
    </item>
    <item>
      <title>Lock-Free Programming – From Primitives To Working Structures</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Sun, 15 Jun 2025 19:05:39 +0000</pubDate>
      <link>https://dev.to/pasksoftware/lock-free-programming-from-primitives-to-working-structures-180</link>
      <guid>https://dev.to/pasksoftware/lock-free-programming-from-primitives-to-working-structures-180</guid>
      <description>&lt;p&gt;Working with multiple threads is one of the most complex problems we may encounter in our daily work. When put against the wall of multithreading most people right away reach out for blocking approaches. In Java, it takes the form synchronized keyword, or some other less painful mechanisms, like ReentrantLock. Lock are not the only option: Lock-Free programming is also the way.&lt;/p&gt;

&lt;p&gt;In this text, I will show problems, techniques and best practices related to Lock-Free Programming. I will also provide a real life example of how to implement a Lock-Free stack. Besides, I will share common patterns on moving from Lock-Free to Wait-Free.&lt;/p&gt;




&lt;p&gt;Here you can find only the most interesting code samples. The full source code is available in my &lt;a href="https://github.com/Pask423/articles-misc/tree/master/lock-free" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Lock Free Programming?
&lt;/h2&gt;

&lt;p&gt;Guarantee: Lock-free programming algorithms guarantees system-wide progress. Within a finite number of steps by all threads, at least one thread completes its operation.&lt;/p&gt;

&lt;p&gt;Typical techniques: atomic primitives CAS, LL/SC, FAA.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scales well under contention because there is no kernel blocking or context-switch overhead.&lt;/li&gt;
&lt;li&gt;Eliminate deadlocks and livelocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Harder to design and reason about.&lt;/li&gt;
&lt;li&gt;Higher cost under very low contention compared with a simple mutex.&lt;/li&gt;
&lt;li&gt;Starvation is still possible—one unlucky thread might repeatedly fail while others succeed, but the program as a whole keeps moving forward. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Wait-Free Programming?
&lt;/h2&gt;

&lt;p&gt;Guarantee: Wait-Free algorithm provides a per-thread progress bound. Every thread finishes its operation in a finite number of its own steps, regardless of what the other threads do.&lt;/p&gt;

&lt;p&gt;Typical techniques: Per-thread operation descriptors and finite step helper functions&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All from Lock-Free Programming&lt;/li&gt;
&lt;li&gt;Real-time friendly&lt;/li&gt;
&lt;li&gt;Eliminate Starvation problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Significantly more memory and bookkeeping.&lt;/li&gt;
&lt;li&gt;Often slower in the uncontended “happy path”&lt;/li&gt;
&lt;li&gt;Harder (sometimes impossible) to create for complex data structures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Blocking vs Lock-Free vs Wait-Free – The Progress Guarantees
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deadlock
&lt;/h3&gt;

&lt;p&gt;Deadlock is probably the most common error in the multithreading. Two (or more) threads each hold resources the others need and wait forever for those resources to be released. Our threads will not be able to move forward with processing. Thus, we end with stand-still — a Deadlock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Thread A: lock(m1); lock(m2);
    Thread B: lock(m2); lock(m1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If A acquires m1 and B acquires a m2, both block forever on the second lock().&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ftb0nt5mscoy2gtdfeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ftb0nt5mscoy2gtdfeh.png" alt="Dead lock" width="576" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Livelock
&lt;/h3&gt;

&lt;p&gt;A livelock is motion without progress. Like two people in a hallway, sidestepping left and right in perfect sync and never getting past each other. Threads are active—spinning, retrying, sending messages, but the system never completes a useful operation. Usually caused by over-reactive collision‐avoidance or continual retries without an eventual terminal state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fmy0kn9iu6f2zjsfbem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fmy0kn9iu6f2zjsfbem.png" alt="livelock" width="576" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Starvation
&lt;/h3&gt;

&lt;p&gt;Starvation means a particular thread never gets the resources or CPU time it needs. Even despite the fact that the system as a whole keeps making progress. If other threads hit the same data structure with high frequency. Then one of the threads may have to retry many times because its compare-and-swap keeps failing. This high contention can cause the Thread to starve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb5xsalhfoc5dar8kjog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb5xsalhfoc5dar8kjog.png" alt="starvation" width="576" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Lock-Free Programming&lt;/th&gt;
&lt;th&gt;Wait-Free Programming&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deadlock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Impossible.&lt;/strong&gt; No thread ever waits while holding a lock, so circular-wait conditions can’t arise.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Impossible.&lt;/strong&gt; Same lock-free property plus bounded completion time for every thread.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Livelock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Prevented.&lt;/strong&gt; At least one operation must complete in a finite number of steps, so the system can’t get stuck in endless mutual retries.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Prevented.&lt;/strong&gt; Every operation finishes in a bounded number of steps, so the whole system and each thread move forward.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Starvation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Possible.&lt;/strong&gt; System makes progress, but an unlucky thread may be perpetually overtaken by others.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Impossible.&lt;/strong&gt; Each thread completes within a fixed bound, so no one can be starved.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Atomic Primitives That Power Non-Blocking Algorithms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compare-and-Swap (CAS)
&lt;/h3&gt;

&lt;p&gt;Compare-and-Swap (CAS) is probably the single most famous atomic instruction. In one atomic step, the processor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads a memory word&lt;/li&gt;
&lt;li&gt;Checks whether that word still holds an expected value&lt;/li&gt;
&lt;li&gt;Only if the comparison succeeds replace it with a new value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That simple “check-and-set” sequence lets a thread act as if it briefly owned the variable without ever locking it.&lt;/p&gt;

&lt;p&gt;Most if not all non-blocking datastructures rely on this instruction mix with a loop to work correctly. You will see one of them below — in form of &lt;strong&gt;LockFreeStack&lt;/strong&gt;. However, there is a catch or two hidden here. First when contention is rising, the loop starts taking more and more time to complete. The other is known as a A-B-A problem.&lt;/p&gt;

&lt;p&gt;In Java, we all &lt;strong&gt;Atomic&lt;/strong&gt; classes, have &lt;strong&gt;compareAndSwap&lt;/strong&gt; methods. Additionally, you can try to emulate CAS with usage of &lt;strong&gt;volatile&lt;/strong&gt; keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load-Link / Store-Conditional (LL/SC)
&lt;/h3&gt;

&lt;p&gt;One of the ways to address the A-B-A problem from above is the LC/SC instruction pair. It is a pair of two steps —atomic with respect to other threads. Effectively creating a “split CAS.”&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Load the word at addr and place the CPU into a reservation state for that cache line.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If reservation valid, no intervening write by another core, stores new value, otherwise it does nothing and returns false.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main difference between CAS and LL/SC is a reservation mechanism. Thus, it dodges the ABA problem. Unfortunately,&lt;br&gt;&lt;br&gt;
 LL/SC is not a silver bullet and has drawbacks. The biggest one is that the Reservation can be lost for reasons&lt;br&gt;&lt;br&gt;
 other than contention. In such a case we lose all the benefits of using this primitive.&lt;/p&gt;

&lt;p&gt;In java the closest thing to LL/SC is &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/atomic/AtomicStampedReference.html" rel="noopener noreferrer"&gt;AtomicStampedReference&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fetch-and-Add (FAA)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fetch-and-Add&lt;/strong&gt; , or &lt;strong&gt;AtomicAdd&lt;/strong&gt; perform a following operations in single atomic step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the current value at addr.&lt;/li&gt;
&lt;li&gt;Adds a delta k.&lt;/li&gt;
&lt;li&gt;Stores the result back.&lt;/li&gt;
&lt;li&gt;Returns the old value (some variants return the new one).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are other variants of this primitive like: &lt;strong&gt;Fetch-and-Subtract&lt;/strong&gt;. FAA is used as a base in more complex techniques like Fetch-And-Store or Test-And-Set.&lt;/p&gt;

&lt;p&gt;The main disadvantage of this primitive is, what name suggests. FAA only supports arithmetic transforms of the form &lt;strong&gt;old + k&lt;/strong&gt;. To do anything more complex you will still need CAS or LL/SC.&lt;/p&gt;

&lt;p&gt;In Java all &lt;strong&gt;Atomic&lt;/strong&gt; classes, have expose &lt;strong&gt;getAndIncrement&lt;/strong&gt; or &lt;strong&gt;incrementAndGet&lt;/strong&gt; methods.&lt;/p&gt;
&lt;h3&gt;
  
  
  Double-Width CAS
&lt;/h3&gt;

&lt;p&gt;This is an extension of classic CAS. Double Width CAS (or DCAS) extends the regular CAS idea to two adjacent machine words treated as a single 128-bit (or larger) unit. Both sub-words must match their expected values for the store to occur.&lt;/p&gt;

&lt;p&gt;In pseudocode&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    cas2(expected_lo, expected_hi, new_lo, new_hi);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It allows you to store a pointer in the low word and a tag in the high word. Both words must match, so resurrecting an old pointer with the same address, but a different tag fails. Thus solving the A-B-A problem.&lt;/p&gt;

&lt;p&gt;DCAS enables part of lock-free datastructures to swap pairs (pointer/counter or value/status) atomically. The main drawback is heavier micro-architectural cost than single-word CAS (locks 16-byte cache line)&lt;/p&gt;

&lt;h3&gt;
  
  
  Primitives Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primitive&lt;/th&gt;
&lt;th&gt;General form&lt;/th&gt;
&lt;th&gt;Typical uses&lt;/th&gt;
&lt;th&gt;Common pitfalls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CAS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;old ⇒ new&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Universal lock-free building block&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ABA problem&lt;/strong&gt; , retry cost under contention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DW-CAS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(old_lo, old_hi) ⇒ (new_lo, new_hi)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pointer + tag pairs, ABA defense&lt;/td&gt;
&lt;td&gt;Heavier locks 16-byte cache line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LL/SC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LL; …; SC&lt;/code&gt; split&lt;/td&gt;
&lt;td&gt;Portable lock-free ops when CAS not present&lt;/td&gt;
&lt;td&gt;Reservation loss → more retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FAA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new = old + k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counters, ticket locks, ref-counts&lt;/td&gt;
&lt;td&gt;Cache-line ping-pong, limited to arithmetic ops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Beside these 4 primitives we have more complex ones like Atomic Exchange / Test-and-Set (TAS), Fetch-and-store (FAS).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock-Free Programming In Java – Lock-Free Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Base
&lt;/h3&gt;

&lt;p&gt;Simplest Lock Free Stack, with single atomic top pointer. Push and pop CAS loops operate on that pointer. It is a singly linked Stack thus the Node class only contains &lt;strong&gt;next&lt;/strong&gt; reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.util.concurrent.atomic.AtomicReference;

private record Node(E value, Node next) {

}

private final AtomicReference top = new AtomicReference&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lock-Free Push
&lt;/h3&gt;

&lt;p&gt;Here we have a lock free push. You can see the CAS loop wrap around the top pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* ---------------- push ----------------------- */
public void push(E item) {
    Node newNode;
    Node oldTop;
    do {
        oldTop = top.get();
        newNode = new Node&amp;lt;&amp;gt;(item, oldTop);
    } while (!top.compareAndSet(oldTop, newNode));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the current head pointer.&lt;/li&gt;
&lt;li&gt;Build the replacement node – safe because nothing else can see newNode yet.&lt;/li&gt;
&lt;li&gt;CAS attempt cas(oldTop, newNode).&lt;/li&gt;
&lt;li&gt;If no one has changed top in the meantime, the CAS succeeds, and the loop exits.&lt;/li&gt;
&lt;li&gt;If another thread slipped in, the CAS fails, we loop to re-snapshot the new top.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lock-Free Pop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* ---------------- pop ----------------------- */
public E pop() {
    Node oldTop;
    Node newTop;
    do {
        oldTop = top.get();
        if (oldTop == null) return null;
         newTop = oldTop.next();
    } while (!top.compareAndSet(oldTop, newTop));
    return oldTop.value();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the current head pointer.&lt;/li&gt;
&lt;li&gt;Null-check nothing to pop -&amp;gt; return immediately, no CAS needed.&lt;/li&gt;
&lt;li&gt;Assign a new top.&lt;/li&gt;
&lt;li&gt;CAS from oldTop -&amp;gt; newTop&lt;/li&gt;
&lt;li&gt;If no one has changed top in the meantime, the CAS succeeds, we atomically detach oldTop.&lt;/li&gt;
&lt;li&gt;If another thread slipped in, the CAS fails, we loop to re-snapshot the new top.&lt;/li&gt;
&lt;li&gt;Return — safe because oldTop is now private to this thread, GC will eventually reclaim it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  From Lock-Free Programming to Wait-Free Programming
&lt;/h2&gt;

&lt;p&gt;I won't be presenting a complete Wait-Free implementation. It is long, complex and explaining it correctly will probably double the size of this blog. However, there are a few good sources on building Wait-Free datastructures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queues

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://scispace.com/pdf/scalable-synchronous-queues-2wgromqh5i.pdf" rel="noopener noreferrer"&gt;Scalable Synchronous Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chaoran.me/assets/pdf/wfq-ppopp16.pdf" rel="noopener noreferrer"&gt;Wait-Free Queue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Stack

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/1510.00116" rel="noopener noreferrer"&gt;A Wait-Free Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.tau.ac.il/~mad/publications/podc06.pdf" rel="noopener noreferrer"&gt;Fetch-And-Add Wait free Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/1510.00116" rel="noopener noreferrer"&gt;general&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;There are a couple of common points shared between all Wait-Free implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Op Log — Each thread writes a small record describing what it wants to do.&lt;/li&gt;
&lt;li&gt;Per-thread slot reservation → Thread grabs a unique index with FAA; writes its OpLogs there.&lt;/li&gt;
&lt;li&gt;Phase (ticket) numbers → Every request gets a monotonically increasing “phase”.&lt;/li&gt;
&lt;li&gt;Bounded Help loops → A helper will finish at most k foreign operations before returning to its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As you could see, both lock-free and wait-free are not simple things. However, with a couple of good tools and techniques, they can be much easier.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Definitions

&lt;ul&gt;
&lt;li&gt;Lock-free programming → from all active threads, at least one makes progress in a finite number of steps. &lt;/li&gt;
&lt;li&gt;Wait-free programming→ every operation by every thread completes in a bounded number of its own steps. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Atomic Primitives

&lt;ul&gt;
&lt;li&gt;CAS&lt;/li&gt;
&lt;li&gt;LL/Sc&lt;/li&gt;
&lt;li&gt;FAA&lt;/li&gt;
&lt;li&gt;Double-width CAS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Best practises in implementing Lock-Free and Wait-Free structures:

&lt;ul&gt;
&lt;li&gt;Design first, code later → Draw the state diagram and identify every location that can change concurrently.&lt;/li&gt;
&lt;li&gt;One atomic word to one logical invariant → Keep each CAS/LL-SC operating on the entire state you need to test.&lt;/li&gt;
&lt;li&gt;Use DCAS → rather than splitting an invariant across two variables.&lt;/li&gt;
&lt;li&gt;Write the fast path first → Attempt a cheap single-CAS path first; after N failures publish a descriptor and switch to the helping (“slow”) path.&lt;/li&gt;
&lt;li&gt;Bounded helping (Wait-Free) Ensure a helper can finish at most k foreign operations before returning to its own.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Thank, you for your time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/lock-free-programming/" rel="noopener noreferrer"&gt;Lock-Free Programming – From Primitives To Working Structures&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Software Engineering Trade-Offs</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Sat, 14 Jun 2025 15:43:55 +0000</pubDate>
      <link>https://dev.to/pasksoftware/software-engineering-trade-offs-1plh</link>
      <guid>https://dev.to/pasksoftware/software-engineering-trade-offs-1plh</guid>
      <description>&lt;p&gt;In a couple of my last articles, I emphasize the importance of different software engineering trade-offs, for &lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;example here&lt;/a&gt;. I have been trying to point out that focusing on maxing out just one trait can cause problems in others. I believe that main part of our job as software engineers should be to min-max different software engineering trade-offs and even the trade-offs of different combinations of trade-offs.&lt;/p&gt;

&lt;p&gt;Software engineering is an art of constantly balancing all these things. Below you can find eight trade-offs, plus their pros and cons. I will also share a very simple framework for navigating software engineering trade-offs.&lt;/p&gt;

&lt;p&gt;First, a reality check: perfection is impossible — min-maxing is the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;We Cannot Build Perfect&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the perfect case we could build a system that matches each and every requirement. It could also handle all the possible edge-cases and yet be simple and easy to maintain. Well, reality is often disappointing: each new case our the system can handle increasing its complexity. Each new fancy tech, tool or concept we introduce, will do the same.&lt;/p&gt;

&lt;p&gt;If we are choosing data transfer format, we can pick one of the few—but not all of them. Of course, can decide to add support for all the formats, but again the complexity increases.&lt;/p&gt;

&lt;p&gt;If we want our system to be based only on stateful operations, we cannot expect that the system will be easily scalable. We can then off-load part of stateful processing to other services or tools, but again complexity follows.&lt;/p&gt;

&lt;p&gt;Unfortunately, the software engineering is far from perfect. Same as in life—each action/decision has a consequence, either short or long-lasting. We cannot run away from that fact. Luckily for us, in software engineering the boundaries are much more flexible, and the consequences are not as dire as in personal life.&lt;/p&gt;

&lt;p&gt;In the worse case, we can always build something from scratch. It will not be cheap, easily or even fast, but it is always a possibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Software Engineering Trade-Offs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s start with my favourite trade-off: Complexity vs Everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Complexity vs Everything&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This one is as simple as it can. I wrote a lot about this one in the paragraph above, and I do not want to repeat myself. Almost every decision we make increases complexity, that’s it.&lt;/p&gt;

&lt;p&gt;With time, the complexity grows, and the growth speed only increases. The system is complex enough to begin with, and we want it to support newer and newer use cases.&lt;/p&gt;

&lt;p&gt;As software engineers, we have to keep complexity as low as possible. In ideal case we should also leave some margin for future changes and requirements.&lt;/p&gt;

&lt;p&gt;Cons of high complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase RTB (Run the Business) costs&lt;/li&gt;
&lt;li&gt;Increase onboarding cost&lt;/li&gt;
&lt;li&gt;Increase costs of new change&lt;/li&gt;
&lt;li&gt;Chance the system will become either unmaintainable or unreplaceable (at least without huge investment of time and money)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you discover any real benefit of increased complexity, I owe you a coffee.&lt;/p&gt;

&lt;p&gt;To be 100 % clear, complexity is not something we can fully run away from. It is a trait of every system. We just should be aware of it and balance our choices accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simplicity vs Flexibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simplicity is a key always and everywhere.&lt;/p&gt;

&lt;p&gt;I guess that most of us will prefer to work with easy to grasp and easy to maintained systems. I also guess that most of us prefer to design systems that are just like that. However, we must not oversimply our architectures. We should always leave some design margin for future changes.&lt;/p&gt;

&lt;p&gt;Yet, making the system too flexible is also a no-go, at least in my opinion. There is no point in making your system capable of handling all possible future scenarios from the start. Half of what you expected will not ever occur, and the other half will be significantly different from what you expected. I will quote a proverb No big design up front.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Simplicity&lt;/th&gt;
&lt;th&gt;Flexibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Lower onboarding time  &lt;br&gt; - Fewer things can fail  &lt;br&gt; - Easier to reason about and maintain&lt;/td&gt;
&lt;td&gt;- Easier to extend  &lt;br&gt; - Easier to cover unexpected requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- May require architecture rewrites sooner rather than later  &lt;br&gt; - Less open to change&lt;/td&gt;
&lt;td&gt;- Harder to reason about  &lt;br&gt; - Potentially harder to test  &lt;br&gt; - “Just-in-case” code bloats the codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Time-to-Market vs Technical Debt&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Time to market vs. Technical Debt is probably the most crucial when it comes to actually delivering software.&lt;/p&gt;

&lt;p&gt;Even the most beautiful and perfect code, does not matter if competitors are already there, and they are stealing away our to-be customers. In more corporate cases—we continuously fail to meet our deadlines and deliver on time.&lt;/p&gt;

&lt;p&gt;Time to Market itself does not bring any value. I know that everyone want to be viral since day one but cascading software failure is probably not the desired way to achieve it. Our code has to actuality work and meet customer expectations. Also, the code itself is not the only source of tech debt. Things like observability, security, tests are among other sources.&lt;/p&gt;

&lt;p&gt;Maybe, polishing the code for yet another time is not the best usage of time left. Instead, it may be better to focus on building a good observability pipeline or doing some performance tests.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Time-to-Market&lt;/th&gt;
&lt;th&gt;Technical Debt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Reach customers sooner and seize fleeting market opportunities  &lt;br&gt; - Collect real-world feedback earlier to refine product–market fit  &lt;br&gt; - Generate revenue (or demonstrate traction to investors) faster&lt;/td&gt;
&lt;td&gt;- Clean, well-tested architecture lowers long-term costs  &lt;br&gt; - Greater reliability, performance, and security from day one  &lt;br&gt; - Future features ship faster because the foundation is solid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Technical debt raises future maintenance and refactor costs  &lt;br&gt; - Increased likelihood of bugs, outages, and security gaps  &lt;br&gt; - Major rewrites can disrupt roadmaps and morale&lt;/td&gt;
&lt;td&gt;- Slower initial launch may cede market share to faster rivals  &lt;br&gt; - Delayed revenue and user feedback increase business risk  &lt;br&gt; - Risk of over-engineering before proving product–market fit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Horizontal vs Vertical Scaling
&lt;/h3&gt;

&lt;p&gt;If you are not sure as what any of them means, I recommend reading &lt;a href="https://pasksoftware.com/scalability/" rel="noopener noreferrer"&gt;my text on Scalability&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Picking the way of how we can scale our application is probably one of the most crucial choices we can make while designing our application. It shapes all core design choices we make in our system and has long-lasting consequences.&lt;/p&gt;

&lt;p&gt;This choice is not a set in stone; you can change the approach later down the road. However, all the architecture changes required to make application horizontally scalable will probably make the whole undertaking long, painful and expensive.&lt;/p&gt;

&lt;p&gt;Same is true in the other way around — if we are migrating from horizontal to vertical. In both case, it will probably end with rewriting the system from scratch, or similar level of changes.&lt;/p&gt;

&lt;p&gt;Horizontal scaling also has drawbacks. This approach also has drawbacks. You can achieve great performance with vertical scaling only.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Vertical&lt;/th&gt;
&lt;th&gt;Horizontal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Smaller ops overhead  &lt;br&gt; - Easier state management  &lt;br&gt; - Lower coordination overhead&lt;/td&gt;
&lt;td&gt;- Practically unbounded scale  &lt;br&gt; - Inherent redundancy  &lt;br&gt; - Supports geo-distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Hard upper limit  &lt;br&gt; - Single point of failure&lt;/td&gt;
&lt;td&gt;- Higher ops overhead  &lt;br&gt; - Susceptible to network-related problems  &lt;br&gt; - Must be designed with distribution in mind&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Latency vs Throughput
&lt;/h3&gt;

&lt;p&gt;This trade-off may seem strange. One would think that optimization of latency—single request processing time would impact the overall throughput—number of requests we can handle per unit of time.&lt;/p&gt;

&lt;p&gt;Surprise, surprise after a certain point it seems not to be the case.&lt;/p&gt;

&lt;p&gt;Optimizing and fine-tuning for latency tends to concentrate extra CPU cycles, cache space, or memory bandwidth on a single request. While it may yield great results initially, after a certain, non-arbitrary, threshold this results tends to diminish. After that point achieving any measurable gains can even require hardware or architectural changes.&lt;/p&gt;

&lt;p&gt;In the case of Throughput we tend to split the resources proportionally. Focusing on optimizing average processing time across multiple requests. Instead of aiming at absolute latency of any one request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Better tail behavior  &lt;br&gt; - More predictable&lt;/td&gt;
&lt;td&gt;- Steady hardware utilization  &lt;br&gt; - Less complex (in theory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Computation-heavy  &lt;br&gt; - Extra resources tied to a single request  &lt;br&gt; - Throughput ceiling&lt;/td&gt;
&lt;td&gt;- Tail spikes / less predictable UX  &lt;br&gt; - Slower single-request response  &lt;br&gt; - Susceptible to back-pressure problems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As this trade-off can be somewhat tricky I recommend deciding based on what your use case needs. If you have some type of mixed use case, or focus point is not clear then I would recommend using or slightly optimizing your SLO (e.g. p99 latency). Only then focusing on throughput subjected to that SLO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateful vs Stateless
&lt;/h3&gt;

&lt;p&gt;To be honest, we cannot truly run away from stateful processing. Unless we have a very specific use case, we would need some form of state. The real trade-off here is to either store state in our service, close to our logic, or we want to offload it to some 3rd party tool or platform.&lt;/p&gt;

&lt;p&gt;As some of the other software engineering trade-offs this one will also have a major impact on our system final design. Among other, it will impact areas like scalability, load balancing and overall complexity of the system. I will dive deeper into this topic in separate text.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Stateful&lt;/th&gt;
&lt;th&gt;Stateless&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Easier to build for strong consistency  &lt;br&gt; - Less communication (state is on the server)&lt;/td&gt;
&lt;td&gt;- Elastic scaling  &lt;br&gt; - Fault tolerance by default  &lt;br&gt; - Open to composability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Harder failover  &lt;br&gt; - Can only scale vertically  &lt;br&gt; - Higher operational overhead&lt;/td&gt;
&lt;td&gt;- Added complexity  &lt;br&gt; - More communication required  &lt;br&gt; - More complex retries and deduplication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sync (Blocking) vs Async (Non Blocking)
&lt;/h3&gt;

&lt;p&gt;Every network call, disk seek, and every RPC is happening asynchronously, in the background, at hardware level. This is the fact we cannot run away from.&lt;/p&gt;

&lt;p&gt;The real trade-off is whether we expose that fact. Make our stack non-blocking (async) or hide this fact behind blocking (sync) API.&lt;/p&gt;

&lt;p&gt;Opposite to the other trade-offs here this one has, relatively small impact on the overall architecture. However, it has a more significant impact on our codebase, and how our code works.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Sync (Blocking)&lt;/th&gt;
&lt;th&gt;Async (Non-Blocking)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Easier to reason about  &lt;br&gt; - Easier to debug  &lt;br&gt; - Easier to set up&lt;/td&gt;
&lt;td&gt;- Better resource utilization  &lt;br&gt; - Well-suited for concurrency/multithreading  &lt;br&gt; - Efficient at handling multiple I/O calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Wasted resources while idle  &lt;br&gt; - Poor performance with multiple I/O operations&lt;/td&gt;
&lt;td&gt;- Harder to reason about  &lt;br&gt; - Risk of callback hell  &lt;br&gt; - More complex to set up and debug&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While non-blocking may seem to be the clear winner here, it is not that simple. Complexity introduced by async may not seem so bad. Nonetheless, it is a totally different programming model than what we used to. In most cases it will require a completely new mindset.&lt;/p&gt;

&lt;p&gt;Beware, the tricky part, async models do not always outperform sync models for CPU-bound tasks.&lt;/p&gt;

&lt;p&gt;I think that a good approach is to use a sync model in the core of the code base. Then using an async model in the edges when you need to handle I/O tasks. I believe this mix will get most of the pros of both approaches. Besides, it will also leave our core/domain pure, and play very nice with hexagonal arch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coupling and Cohesion
&lt;/h3&gt;

&lt;p&gt;Though we used to think of them when talking about microservices. These two metrics in fact can be used to describe any type of architecture. No matter its size. We can even use it to describe relations between classes in the source code of a particular service.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coupling&lt;/strong&gt; describes the interdependence between two modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cohesion&lt;/strong&gt; describes how well the elements of a module belong together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not the trade-off per se more like a target we should aim to. No matter where we apply both of the concepts the relation between them should be the same. Our entities should have: &lt;strong&gt;High Cohesion and Low (Loose) Coupling&lt;/strong&gt;. Any other relation between the concepts is unhealthy and will cause problems.&lt;/p&gt;

&lt;p&gt;Our job is to correctly adjust the levels of Coupling and Cohesion not to overdo any of them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Coupling&lt;/th&gt;
&lt;th&gt;Cohesion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Fault isolation  &lt;br&gt; - Independent deployment and scaling&lt;/td&gt;
&lt;td&gt;- Focused services/modules  &lt;br&gt; - Higher stability (fewer sources of change)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Nano-services: too low coupling  &lt;br&gt; - Big ball of mud: too high coupling&lt;/td&gt;
&lt;td&gt;- Unrelated domains mix, higher volatility (too low cohesion)  &lt;br&gt; - Potential duplication and limited code reuse (too high cohesion)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;p&gt;These are not the only software engineering trade-offs out there — there are many more other ones. In fact, most if not all the decisions we make while designing the system are trade-offs.&lt;/p&gt;

&lt;p&gt;Below a few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency vs Availability — probably the most famous. &lt;a href="https://pasksoftware.com/modular-monolith/" rel="noopener noreferrer"&gt;Microservice vs Monolith&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;3rd Tools vs In-house &lt;a href="https://world.hey.com/dhh/we-have-left-the-cloud-251760fb" rel="noopener noreferrer"&gt;Cloud vs On-premise&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Security vs Usability&lt;/li&gt;
&lt;li&gt;Read vs Write Optimize&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Navigating Software Engineering Trade-Offs
&lt;/h2&gt;

&lt;p&gt;While they are not complex, long, and covers all possible edge cases, below rules are simple, cohesive, and easy to follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluate the short-term and long-term impact of decisions
&lt;/h3&gt;

&lt;p&gt;First and the most important rule — aim for the long-term.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-term gains are tempting — however they may have hidden costs and cause a lot of pain later on.&lt;/li&gt;
&lt;li&gt;Estimate lifetime — services/modules/systems may not live long enough to see long-term at all.&lt;/li&gt;
&lt;li&gt;Use data whenever you have them — without data you are just another person with an opinion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Identify key stakeholders, their needs and act accordingly.
&lt;/h3&gt;

&lt;p&gt;If you ever worked on any project with more than average complexity then you probably know that there are multiple people interested in its success (or failure). It is impossible to meet everyone's expectations.&lt;/p&gt;

&lt;p&gt;Thus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Map all interested parties&lt;/li&gt;
&lt;li&gt;Prioritize the critical few who will approve or reject the outcome.&lt;/li&gt;
&lt;li&gt;Capture their expectations and success criteria&lt;/li&gt;
&lt;li&gt;Try to favor the side of trade-offs in such a way to meet their expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Research — clarify goals and hard constraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requirements are not always clear, verify them.&lt;/li&gt;
&lt;li&gt;When you have high level requirements, try to come up with an initial design.&lt;/li&gt;
&lt;li&gt;Show you design to stakeholders and reiterate the requirements&lt;/li&gt;
&lt;li&gt;Attempt to quantify metrics like: latency, throughput, storage whenever possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can, try doing &lt;strong&gt;Event Storming&lt;/strong&gt; session. Crucial info has the tendency to show up in the most unexpected of times and places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt; : the more knowledge you will gather the easier it will be for you to navigate your landscape of trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document trade-offs, and their rationale (ADR)
&lt;/h3&gt;

&lt;p&gt;Document, document and once again &lt;strong&gt;DOCUMENT&lt;/strong&gt;. While it may sound trivial and repetitive it is probably the single most important thing you can do for your future coworkers.&lt;/p&gt;

&lt;p&gt;Leaving behind even the simplest Architecture Decision Record (ADR), with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What you chose&lt;/li&gt;
&lt;li&gt;Why you did it&lt;/li&gt;
&lt;li&gt;Pros and cons (optionally)&lt;/li&gt;
&lt;li&gt;Alternatives considered and rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such a document will ease up many, many things. Not to mention building your team reputation among whoever comes next to the project.&lt;/p&gt;

&lt;p&gt;In the worst case, it spares future engineers from head-scratching, and from muttering unspeakables at 2 a.m. Which is probably the best measure of code quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prototype or spike the extreme options to expose hidden pitfalls early.
&lt;/h3&gt;

&lt;p&gt;If you think that you are lacking knowledge in some particular topic, or you are unsure as to how solutions would work. Try to spend some time and prepare a POC or do some spiking around the topic.&lt;/p&gt;

&lt;p&gt;Better to drop some approaches sooner than later. It will cost less and be less painful. Just remember no to spend too much time on this, it should be POC not a fully working system, keep it simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on simple solutions then optimize for the future.
&lt;/h3&gt;

&lt;p&gt;As a final piece of advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start from the simplest solution that meets current requirements&lt;/li&gt;
&lt;li&gt;Optimize and make it more extensible only after completing the previous step.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this way, you should end up with a well min-maxed system. It should meet all the requirements, be slightly optimized and had some free design space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;All of these may seem complex, hard or even overwhelming. Yes it is complex, there are a lot of software engineering trade-offs. However, there are multiple guides and best practices on how to navigate the problems of system design. I have even shared my own.&lt;/p&gt;

&lt;p&gt;As with a many other things — practice makes perfect. There are multiple case-studies, books and articles on how to approach design challenges in different types of systems. I mention one of them &lt;a href="https://pasksoftware.com/software-engineer-books/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I believe that after some practice, all the problems here will sound significantly less scary.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;Software Engineering Trade-Offs&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>tradeoffs</category>
      <category>design</category>
    </item>
    <item>
      <title>Building Software Engineer Library</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Thu, 17 Apr 2025 21:21:06 +0000</pubDate>
      <link>https://dev.to/pasksoftware/building-software-engineer-library-1oig</link>
      <guid>https://dev.to/pasksoftware/building-software-engineer-library-1oig</guid>
      <description>&lt;p&gt;I believe that every one of us, software engineers, should have our own personal library of software engineer books. Whether in old plain-text book form or in a newer, more eco-friendly electronic one is an open question. The important thing is to actually have one.&lt;/p&gt;

&lt;p&gt;I am one of those strange people that believe that we people in general should read books. Doing so has multiple benefits, but let’s not dive too deep into this and focus on software engineering.&lt;/p&gt;

&lt;p&gt;Well, there are a couple of problems with software engineer books:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They get old rather quickly&lt;/li&gt;
&lt;li&gt;There are a lot of them&lt;/li&gt;
&lt;li&gt;They are expensive&lt;/li&gt;
&lt;li&gt;They have varying levels of quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given our limited time, the obvious conclusion is that it is hard to find a book worthy of reading, one we will not waste our money on. Here comes this article. It will be the first in a series focused on what books I recommend you include in your professional library.&lt;/p&gt;

&lt;p&gt;This particular blog covers books that focus on the softer parts of our job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to grow your career,&lt;/li&gt;
&lt;li&gt;How to approach your work&lt;/li&gt;
&lt;li&gt;Various other problems that we encounter on our professional journey.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have also included one more technical book, which I believe will be a good starting point in software architecture.&lt;/p&gt;

&lt;p&gt;As to why I do not recommend any algorithmic books, it is simple – I just do not like them. Better go to LeetCode; they have a very good crash course on &lt;a href="https://leetcode.com/explore/featured/card/leetcodes-interview-crash-course-data-structures-and-algorithms/" rel="noopener noreferrer"&gt;DSA&lt;/a&gt;. I tried it myself, and I strongly recommend it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This article is not sponsored in any way, shape or form.&lt;/li&gt;
&lt;li&gt;I have read most of the books in this and the following articles. If this is not the case for one of the books, I will explicitly mention it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Software Engineer Books&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Software Craftsman&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.amazon.co.uk/Software-Craftsman-Professionalism-Pragmatism-Robert-ebook/dp/B00QXAGIDO" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Software Craftsman, The: Professionalism, Pragmatism, Pride&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by &lt;strong&gt;Sandro Mancuso&lt;/strong&gt; ends as my first recommendation.&lt;/p&gt;

&lt;p&gt;It is one of my most favorite job-related books. Inside, the author speaks a lot about the importance of professionalism and continuous learning in our work. He also describes how applying both of these concepts can help us grow and be better engineers. Additionally, the author advocates for treating software engineering as a craft, following high coding standards, and taking pride in our work.&lt;/p&gt;

&lt;p&gt;All of this is intersected with different retrospectives from his professional experiences and situations in his life. What is very interesting, the author puts the emphasis on being pragmatic first and idealist later. While idealism is important, the book stresses the need for practical solutions that actually work in real-world scenarios.&lt;/p&gt;

&lt;p&gt;For me, one of the most important takeaways from this book is: &lt;strong&gt;Balancing idealism with pragmatism is essential for success.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Clean Coder&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.amazon.co.uk/Clean-Coder-Conduct-Professional-Programmers-ebook/dp/B0050JLC9Y" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;The Clean Coder: A Code of Conduct for Professional Programmers&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by Robert C. Martin gets the second place on my recommendation list.&lt;/p&gt;

&lt;p&gt;This book is very similar to the previous one. What I mean by this is that it focuses on professionalism, praises continuous learning, discipline and advocates for keeping high quality of work. Similar to &lt;em&gt;Software Craftsman&lt;/em&gt;, the author enriches the text with anecdotes from his own life experiences.&lt;/p&gt;

&lt;p&gt;This is interesting in and of itself, at least for me, as some situations are pretty old, and we may see how programming looked when Uncle Bob was starting his career.&lt;/p&gt;

&lt;p&gt;Overall, &lt;em&gt;The Clean Coder&lt;/em&gt; serves as both a practical guide and a motivational book.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Software Engineer’s Guidebook&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, we have &lt;a href="https://www.amazon.co.uk/Software-Engineers-Guidebook-Navigating-positions-ebook/dp/B0CV6ZNLLP" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;The Software Engineer’s Guidebook&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;, by Gergely Orosz. Its main point of focus is career growth and professional development. This time there are no anecdotes and retrospectives, but we still get a lot of useful recommendations.&lt;/p&gt;

&lt;p&gt;While this book seems more focused on growing and advancing your career as a Senior Engineer, there are a couple of chapters that can also be interesting for those of you with less experience. Especially chapters 1 and 2, which focus mostly on how to navigate your career and how to be a good Engineer.&lt;/p&gt;

&lt;p&gt;Additionally, the third chapter speaks on the quality of a good Senior Engineer, which may also be helpful while planning your career as a junior or mid-level programmer.&lt;/p&gt;

&lt;p&gt;This book provides one of the best career-building frameworks I have ever seen. Despite the fact that I put it here as the 3rd one, I actually recommend you read it first.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;System Design Interview&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://www.amazon.co.uk/System-Design-Interview-Insiders-Guide-ebook/dp/B08B3FWYBX" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;System Design Interview – An insider’s guide&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by Alex Xu, we move away from the area of career development software engineers books to more technical ones.&lt;/p&gt;

&lt;p&gt;This book is probably the simplest, and most easily laid-out intro to &lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;system design&lt;/a&gt; and/or architecture I have seen for a long time. Besides that, it also provides some insight into how some of the systems we use in our everyday life actually work.&lt;/p&gt;

&lt;p&gt;In addition to obvious interview tips, like back-of-the-envelope estimates or a framework for approaching the interview itself. It also gives a lot of useful information on how to &lt;a href="https://pasksoftware.com/scalability/" rel="noopener noreferrer"&gt;scale your system&lt;/a&gt;, and introduces quite interesting concepts like consistent hashing or different rate-limiting algorithms. You know, the things that you would rather not meet in day-to-day work.&lt;/p&gt;

&lt;p&gt;I strongly recommend reading it even if you are not actively preparing for an interview. I am sure it will be time well spent, besides it also quite pleasant to read. There is also a second edition of this book; however, I recommend starting with the first one, as the second edition sometimes mentions concepts introduced in the first one.&lt;/p&gt;

&lt;p&gt;While the books above are my personal favorites, and I strongly recommend starting with them, the books below may also be interesting and informative to read. Nevertheless, I would give them a somewhat smaller priority.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Mythical Man-Month&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.amazon.co.uk/Mythical-Man-Month-Anniversary-Software-Engineering-ebook/dp/B00B8USS14" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;The Mythical Man-Month. Essays on Software Engineering&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by &lt;strong&gt;Frederick P. Brooks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This book is a perfect example that some things in this world do not change, no matter how much time passes. For some companies, all the problems described in this book are still valid, even decades after its first publication. There are still people who believe in the old phrase: “nine women can have a baby in one month.”&lt;/p&gt;

&lt;p&gt;If you look carefully enough, you may even notice some of these problems in your organization.&lt;/p&gt;

&lt;p&gt;This fact alone is more than enough for me to recommend this book. Its main lesson—that adding more people to a project does not automatically make it faster—is still one of the most important factors that one must take into consideration while planning anything serious.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Staff Engineer’s Path&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.amazon.co.uk/Staff-Engineers-Path-Tanya-Reilly-ebook/dp/B0BG16Y553" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;The Staff Engineer’s Path: A Guide for Individual Contributors Navigating Growth and Change&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by &lt;strong&gt;Tanya Reilly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This book is quite a challenge for me. It is widely recommended as one of the best books focused on career development, and the challenges we can face while trying to expand beyond coding. Yet it totally did not do the trick for me; I stopped reading around 50%.&lt;/p&gt;

&lt;p&gt;For me, the biggest problem at the time of reading was the feeling you need to work in a specific environment and have specific opportunities for some recommendations from this book to actually be useful. Nevertheless, I recommend you at least give it a try, especially if you have to become a Staff Software Engineer. Maybe you will find it more meaningful than I did.&lt;/p&gt;

&lt;p&gt;There is one important thing about this book: It is aimed mostly at senior software engineers. While you may find it beneficial nonetheless, I believe it still makes sens to add this note.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Gang of Four&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now we have &lt;a href="https://www.amazon.co.uk/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four)&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.&lt;/p&gt;

&lt;p&gt;This one is a more honorable recommendation due to its impact on software engineering as a whole than anything else.&lt;/p&gt;

&lt;p&gt;While the patterns are still valid and remain virtually unchanged through 40-odd years since the book’s publication, most of us will probably use a tiny subset of them. I believe that knowing all 23 patterns described in the book is not the best use of your mental real estate. Besides, there are easier ways to get familiar with design patterns, and they do not involve reading code samples in C++.&lt;/p&gt;

&lt;p&gt;Despite all of this, I think that reading it may be somewhat interesting and insightful. At least it was for me a few years ago when I was reading it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pragmatic Programmer&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Last but not least, we have &lt;a href="https://www.amazon.co.uk/Pragmatic-Programmer-journey-mastery-Anniversary-ebook/dp/B07VRS84D1" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;The Pragmatic Programmer: Your Journey To Mastery&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;, by Andrew Hunt and David Thomas.&lt;/p&gt;

&lt;p&gt;While this book aged better than the previous one, it still may feel a little dated. Mostly due to the fact that a lot of the concepts and approaches described inside were considered novelties at the time of publishing are now widely adopted standards that you probably already know about.&lt;/p&gt;

&lt;p&gt;You may find some aspects described in the book insightful and thought-provoking, but I would not recommend prioritizing it highly. There are more worthy software engineers books for your time out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Software Engineer Books – Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here we are. Below, you can see the table with the order in which I recommend reading the books from above. Of course, do not be afraid to change it as you see fit; it is just written, not set in stone.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Book&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Software Engineer’s Guidebook&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Software Craftsman&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Design Interview&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clean Coder&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Staff Engineer’s Path&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mythical Man-Month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pragmatic Programmer&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gang of Four&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/software-engineer-books/" rel="noopener noreferrer"&gt;Building Software Engineer Library&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengieering</category>
      <category>books</category>
      <category>reading</category>
    </item>
    <item>
      <title>Test Pyramid: Best Practices For A Reliable Test Suite</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Thu, 17 Apr 2025 20:25:07 +0000</pubDate>
      <link>https://dev.to/pasksoftware/test-pyramid-best-practices-for-a-reliable-test-suite-7fh</link>
      <guid>https://dev.to/pasksoftware/test-pyramid-best-practices-for-a-reliable-test-suite-7fh</guid>
      <description>&lt;p&gt;Testing our code is essential for maintaining the high quality of our code. In the long term, tests are crucial to ensure that we have maintainable software at all. Today I will dive into the &lt;strong&gt;Test Pyramid&lt;/strong&gt; and present a way how you can structure your tests to get the most out of them. If you want to know other best practises for tests, &lt;a href="https://pasksoftware.com/first-tests/" rel="noopener noreferrer"&gt;check FIRST&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, before we dive into the Test Pyramid, let’s take a look at different types of tests that we have.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tests Taxonomy&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Test&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simplest test intended to verify correctness for singular methods or functions in isolation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration Test&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify the interaction between different modules our applications have, usually one at a time, identifying issues at the interfaces between integrated parts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E2E Tests&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High-level tests that verify the whole flow correctness, from providing input to validating output on the opposite end. They validate if the application works well as a whole.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smoke Tests&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Very simple tests that run on an up-and-running system, usually just after deploying a new version, to ensure that the most critical features are working as expected—a kind of sanity check of our system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contract Tests&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validate if two sides of some arbitrary interaction are compatible with one another. They check whether the responses from one side of the interaction match the expectations of the other, and vice versa.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Tests&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This type of test verifies if the performance of our applications meets the requirements, usually done on a setup as similar to production as possible and in the scope of the whole system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pen-Test/Security&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A very diverse catch-all term for all the checks and tests that verify the security of our system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chaos Testing/Engineering&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is more an approach than an actual test. Chaos Engineering is aimed at testing system resilience by extreme measures. It works by introducing unpredictable but intentional and traceable failures into the working environment.&lt;/p&gt;

&lt;p&gt;These are not all the types of tests out there, but the exact list depends on whom you ask and how far into categorizing you are willing to get. I believe that the types mentioned above are the most crucial ones, and we will focus on them in today’s text. I also believe that they are the reasonable ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Original Test Pyramid&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Test Pyramid a concept used to describe the test setup to which a system should aspire, visually. It consists of different types of tests. The test types are sorted so that the base is represented by the test type of the highest quantity. Moving higher in the pyramid, each level is represented by the type with a lower number of tests in the overall set.&lt;/p&gt;

&lt;p&gt;In my opinion, the best representation of this test pyramid is presented by Robert C. Martin in his book, &lt;em&gt;The Clean Coder: A Code of Conduct for Professional Programmers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyq8rw3t4nyletpzg6zc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyq8rw3t4nyletpzg6zc4.png" alt="Uncle bob pyramid" width="679" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically, we should have a high number of unit tests as a base, though having only a small set of integration and E2E tests. Performance and security tests are included under System tests.&lt;/p&gt;

&lt;p&gt;This approach has a few good points like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast and cost-effective feedback&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unit tests are fairly easy to set up and, at least by the book, should run quickly, reducing the feedback loop for the developer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is CI/CD friendly&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having fewer complex tests like E2E and integration tests promises that it would be simpler to set up CI/CD jobs. Besides CI runs faster with less integration and E2E tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unit, component and integration tests are less flaky and less complex than full E2E tests. Thus, we have smaller chances of any non-deterministic errors while introducing new tests and/or changing our test environment.&lt;/p&gt;

&lt;p&gt;Additionally, as a whole, the Test Pyramid provides a clear and ready framework on how one should structure tests to get a more reliable system.&lt;/p&gt;

&lt;p&gt;Still, while having all these benefits, it is not free of drawbacks, which I will describe in the following paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why It Is Not Enough&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Well, the first and most important problem in terms of the original test pyramid is the over-reliance on Unit Tests. Such over-reliance introduces a set of problems to our application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Striving to have a high coverage of unit tests in your applications may not necessarily be a good idea. While fast and easy to build, it is very easy to dig too deep into unit testing your code. In such a case, any further changes related to this component may require a lot of additional work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests are not suitable for every project life cycle phase; sometimes even writing proper unit tests may not be possible at all, thus you will have to heavily rely on mocks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The current shape of the pyramid can give a false sense of security, as you have a few tests that actually test the “living, breathing” system. While on unit and integration levels all things may appear right, they may not work correctly as a whole unit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In its current shape, we do not have a large space for non-functional tests, like security tests or performance tests. It also does not mention contract or smoke tests.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Last but not least, remember that the test pyramid is a concept, and as with every concept, there is no need to blindly adhere to it if you do not see any sense. Remove one layer or more of the pyramid if it does not make sense for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Pyramid Per Use Case&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If the original test pyramid is not enough, and I still want to have some guidelines for tests, what then? Well, let’s throw the test pyramid away and just make a priority list of tests. Let’s iterate from the most to the least important type of tests that you need to have. Additionally, let’s make it on a case-by-case basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Change Heavy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s start from the change heavy case. It does not have to be startup, it can be anytype of greenfield or just a new service. Well, here you can go with even zero tests; you probably need velocity and quick customer feedback, not tests. You need freedom to break stuff and rebuild them quickly, not rewriting all the tests from the ground up.&lt;/p&gt;

&lt;p&gt;Here I would recommend focusing on E2E tests for paths that are the most crucial for you. Paths that are your main selling points and competitive advantages. While problematic in case of need for more velocity, I believe such a setup will benefit you the most, and will give you feedback on the operation of your most important parts.&lt;/p&gt;

&lt;p&gt;I would recommend some unit tests if you have some algorithm-heavy or complex logic inside your codebase, especially if it is crucial for your operations and impacts customers directly.&lt;/p&gt;

&lt;p&gt;What is more, I would suggest doing some performance tests before going live—going viral on day one in this way is probably not a desired result.&lt;/p&gt;

&lt;p&gt;If, by some miracle, you still have time to spare, set up some monitoring for the service. Trust me, it will be worth the time and the effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Stable&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Opposite to the change heavy API, where everything may need to be changed and rewritten from scratch, here we have a system without such events—at least not frequently. We have infrequent changes, or the change impacts only a small subset of features.&lt;/p&gt;

&lt;p&gt;In such a case, I would recommend going into the following structure: required integration tests, E2E tests, smoke tests, maybe security and performance tests, and consider contract tests if you are exposing an API.&lt;/p&gt;

&lt;p&gt;Following such a structure will give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-life guarantees as to your system’s operations.&lt;/li&gt;
&lt;li&gt;Freedom to change underlying implementation without the need to change your tests.&lt;/li&gt;
&lt;li&gt;A tool for finding problems in your integrations with 3rd party providers.&lt;/li&gt;
&lt;li&gt;A tool to quickly ensure your system is working correctly after deploying the system.&lt;/li&gt;
&lt;li&gt;A lot of insight from security and performance tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Service Oriented Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This case is kind of a tricky one, as different services may be owned by different teams, and in general, it should be their decision how they want to test their component. However, I believe that there should be a recommendation or best practice to have contract tests for every component, which exposes any type of API. Thanks to following this you will have extract guarantees after any type of change in one of your services.&lt;/p&gt;

&lt;p&gt;If your design is mature enough, you can try introducing chaos engineering and see what results it will yield. System-wide pen-tests can also be a good idea, better done collectively rather than individually. Some additional problems may occur in service as a whole.&lt;/p&gt;

&lt;p&gt;Besides that, I would recommend having systems wide requirements for observability—maybe some preset dashboards, alerts, system-wide best practices. I think that it will give the teams some frameworks they can easily adopt for their unique cases.&lt;/p&gt;

&lt;p&gt;As for the individual services, I would not recommend anything specific; pick the tests that suits your use case the best.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Monolith&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This case is a kind of mix of all the previous ones. I recommend choosing your approach based on how frequent the changes are and what is changing. Remember to take into consideration the coupling between different components inside the &lt;a href="https://pasksoftware.com/modular-monolith/" rel="noopener noreferrer"&gt;monolith&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you frequently change the inside of the monolith, not the interface, then go for E2E tests. On the other hand, if you frequently revise the API, then go for whatever is closer to unit tests you can get. Do the same if you cannot set up E2E in any way, or it is too complex to be actually worth it.&lt;/p&gt;

&lt;p&gt;If there is a high coupling between different components, or the boundaries between them are blurry, maybe try writing something akin to “E2E tests” on a higher component level.&lt;/p&gt;

&lt;p&gt;If it is not there yet, try to set up well-defined logs, metrics, and possible alerts, as close to per-component basis as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Pyramid Common Parts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Besides structures that I mentioned before, there are a couple of different tools that may help you build more reliable systems. Not all of them are mandatory—maybe besides monitoring (this one, in my opinion, is a must-have). Pick the ones that you think will help you.&lt;/p&gt;

&lt;p&gt;However, try to think through all of them; I believe that it will be time well spent nevertheless.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Performance Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While not all systems and modules have strict performance requirements, it may be beneficial to have some performance tests.&lt;/p&gt;

&lt;p&gt;We can provide additional insights for our product or business:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We know how far we can scale if the need arises at some point.&lt;/li&gt;
&lt;li&gt;We can notice that some feature negatively impacts our performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know it may not be the most crucial part for non-critical systems. However, at least we know about the issue and can make a decision on what to do with it instead of just letting it through.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pen-tests / Security Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Again, as with performance tests, not all services and systems require these. Nevertheless, it may be beneficial to at least entertain the idea. You may find some interesting insights along the way. The exact scope and scale greatly depends on a number of various factors. If you want to know more about security, I write on this topic in more &lt;a href="https://pasksoftware.com/category/security/" rel="noopener noreferrer"&gt;detail elsewhere&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ArchUnit Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I think that for all four cases it may be worth to try writing some tests in &lt;a href="https://pasksoftware.com/archunit/" rel="noopener noreferrer"&gt;ArchUnit&lt;/a&gt; fashion. At least when your code structure will stabilize. While it may seem like a wasted time, it will for sure help you keep your code in shape for longer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Observability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Tests are not the only thing that you will need to create robust systems. The whole infrastructure part around your system may be even more crucial than the tests in ensuring flawless operation of your systems.&lt;/p&gt;

&lt;p&gt;As an addition to your tests, you should also have good logging, metrics, and possibly alerts. They will give you additional insight into the operations of your systems. They will also polish some rough edges around your tests and may help identify some bottlenecks not caught in the tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Chaos Engineering/Testing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Probably the most complex concept to implement correctly. While deliberately introducing any type of disruptions or failures into otherwise perfectly working system seem not the brightest idea. It can help identify weaknesses and problems that will not show up in any other case.&lt;/p&gt;

&lt;p&gt;However, this type of “tests” is very, very complex. Introducing failures—no matter if they are intentional or not—is never fully safe. Before going head-on with this, double-check that your software and infrastructure are actually ready to live it through.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Pyramid Trade-off &amp;amp; Considerations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before we jump to the conclusion, there are a couple of trade-offs and assumptions that I think you should take into consideration while picking the tests that you want to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time limits&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the considerations when picking, which tests to focus on is time restrictions. If you have very strict limitations on how long your tests can run, then focusing on unit tests, and some integrations would be better than going for a full E2E test set, and vice versa.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my opinion, a database is not a good case for integration tests nowadays. Integration tests should be used only for 3rd-party services that have complex behavior and cannot be easily tested in E2E tests. If you have such dependencies in your system, then that is, in my opinion, the only valid point to write integration tests. The database layer can be tested in the E2E test layer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I believe that unit tests should only cover the algorithm/logic-heavy pieces of code. There is no point in trying to reach higher coverage tiers with unit tests. In my opinion, it is better to focus on E2E tests. Sometimes, especially for poorly design architectures, writing actual unit tests is much harder than it looks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Setup complexity&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In some cases, it may not be an option to create E2E or unit tests. In such a case, pick the one, which is easier to set up and maintain and gives you more reliability. It may be reasonable to change your architecture/design to be more testable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Over-reliance on mocks&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While writing any type of test, be careful not to overuse mocking and/or stubbing. You can easily start testing mock and stub behaviors instead of the actual code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test implementation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For unit tests, do not go too deep into testing your behavior. Try to test interfaces, not the content of your methods. For E2E tests, try to use as much of the actual components as you can. Do not write your own stubs until you have to, &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;testcontainers&lt;/a&gt;may come in very handy here.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s start with a table to show concepts from previous paragraphs in a clear and concise manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Per Type Of Environment You Want To Run Your Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;Optional&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Change Heavy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- E2E for crucial parts of API  &lt;br&gt; - Good observability pipeline (from logs to alerts)  &lt;br&gt; - Smoke tests for crucial paths&lt;/td&gt;
&lt;td&gt;- Performance tests for crucial parts  &lt;br&gt; - Security tests  &lt;br&gt; - Unit tests for logic/algorithm-heavy parts  &lt;br&gt; - Integration tests for 3rd-party services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- E2E  &lt;br&gt; - Good observability pipeline (from logs to alerts)  &lt;br&gt; - Integration tests for 3rd-party services  &lt;br&gt; - Unit tests for logic/algorithm-heavy parts&lt;/td&gt;
&lt;td&gt;- Performance tests for crucial parts  &lt;br&gt; - Security tests  &lt;br&gt; - Consider if you need Smoke Tests and their scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Oriented Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Contract tests for services exposing APIs used by other services  &lt;br&gt; - Choose exact test setup per service  &lt;br&gt; - Design base observability approaches for each team to adopt and extend&lt;/td&gt;
&lt;td&gt;- System-wide Performance tests  &lt;br&gt; - System-wide Security tests  &lt;br&gt; - Consider Chaos Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monolith&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Pick the tests that are easier to set up and maintain  &lt;br&gt; - Good observability pipeline (from logs to alerts)  &lt;br&gt; - Smoke tests&lt;/td&gt;
&lt;td&gt;- System-wide Performance tests  &lt;br&gt; - System-wide Security tests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Per Test Type&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test Type / Environment&lt;/th&gt;
&lt;th&gt;Change Heavy&lt;/th&gt;
&lt;th&gt;Stable&lt;/th&gt;
&lt;th&gt;Service Based&lt;/th&gt;
&lt;th&gt;Monolith&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Logic heavy methods&lt;/td&gt;
&lt;td&gt;Per service basis&lt;/td&gt;
&lt;td&gt;Depends on the setup cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consider for 3rd party service&lt;/td&gt;
&lt;td&gt;3rd party service&lt;/td&gt;
&lt;td&gt;Per service basis&lt;/td&gt;
&lt;td&gt;3rd party service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;For critical paths&lt;/td&gt;
&lt;td&gt;Mandatory&lt;/td&gt;
&lt;td&gt;Per service basis&lt;/td&gt;
&lt;td&gt;Depends on the setup cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;When and where applicable&lt;/td&gt;
&lt;td&gt;Recommended for all services&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;For consideration&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Per service basis&lt;/td&gt;
&lt;td&gt;System wide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Smoke&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consider for critical path&lt;/td&gt;
&lt;td&gt;Consider for critical path&lt;/td&gt;
&lt;td&gt;Per service basis&lt;/td&gt;
&lt;td&gt;Consider for critical path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;For consideration&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;System wide&lt;/td&gt;
&lt;td&gt;System wide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Predefined rules&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is not a perfect silver bullet for every case—there is no such thing or recommendation. Everything here is based on different &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;trade-offs&lt;/a&gt;, some of them are mentioned in the paragraphs above.&lt;/p&gt;

&lt;p&gt;My final recommendation is: &lt;strong&gt;Just write the best tests that you can, given your design and possibilities.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/test-pyramid-best-practices/" rel="noopener noreferrer"&gt;Test Pyramid: Best Practices For A Reliable Test Suite&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>testing</category>
      <category>test</category>
      <category>api</category>
    </item>
    <item>
      <title>Monolith: The Good, The Bad and The Ugly</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Sun, 13 Apr 2025 18:10:31 +0000</pubDate>
      <link>https://dev.to/pasksoftware/monolith-the-good-the-bad-and-the-ugly-25dk</link>
      <guid>https://dev.to/pasksoftware/monolith-the-good-the-bad-and-the-ugly-25dk</guid>
      <description>&lt;p&gt;After initial very warm welcome, and a wave of hype microservices are no longer considered a silver bullet for all software pitfalls. The plain old monolith approach started to get mainstream attention once again. Especially &lt;strong&gt;Modular Monolith,&lt;/strong&gt; which seem to mix both approaches in the best way. That is why, today I would like to bring it and other monolith subtypes to your attention in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why It Matters?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Well, there are a couple of possible traps, which you may fall prey to while working with the monolith. These traps and their possible consequences, are strongly related to the monolith subtypes I will cover below. These subtypes have their respective names, but I prefer to name them: the good, the bad, and the ugly according to the number of headaches they may cause for its maintainers and owners.&lt;/p&gt;

&lt;p&gt;In a more reasonable way, they go like the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The good&lt;/strong&gt; – the modular monolith&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bad&lt;/strong&gt; – the distributed monolith&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ugly&lt;/strong&gt; – the traditional monolith&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Ugly&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The plain old monolith or the ugly. It has its own problems and quirks – it is hard to maintain, deploy, and has some performance bottlenecks, but at the end of the day, it is doing its job. That’s why I call it ugly. Not the prettiest, but the job is done.&lt;/p&gt;

&lt;p&gt;It is the most common case in monolith implementations as it is a kind of natural way of software progress. When there is not too much to think about long-term problems and consequences, and “now” is more important than “later”.&lt;/p&gt;

&lt;p&gt;Some traits of this subtype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single codebase and deployment unit.&lt;/li&gt;
&lt;li&gt;In-system communications, no outgoing requests.&lt;/li&gt;
&lt;li&gt;Tightly coupled but simpler to understand, up to a certain point.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Bad&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;distributed&lt;/a&gt; monolith or the bad. It is probably the most common anti-pattern when dealing with migration of a monolith to microservices. In particular, it is a result of failed transitions to microservices.&lt;/p&gt;

&lt;p&gt;Essentially, it is a monolith but split across multiple services. There may be more pitfalls hidden behind this, but this one is the most important. While in some cases a distributed monolith may still do its designed job, it is more likely than it will fail.&lt;/p&gt;

&lt;p&gt;Some traits of this subtype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split into multiple services.&lt;/li&gt;
&lt;li&gt;Communication over the network, usually a synchronous one.&lt;/li&gt;
&lt;li&gt;Still tightly coupled, like a monolith, with badly designed responsibilities inside each service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Good&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The modular monolith or the good. It is a monolith with clearly designed boundaries between the modules inside—thus a name, modular monolith. Different modules can basically be developed independently, using only in-place interfaces to call one another.&lt;/p&gt;

&lt;p&gt;It is the best subtype of monolith that exists; usually, it is also ready to be split into microservices when the need arises. It turns some of a monolith’s disadvantages into advantages, yet keeps the whole application as a single deployable unit.&lt;/p&gt;

&lt;p&gt;Such an approach greatly reduces the cognitive and economic burden needed to manage and run microservices. This architecture is easier to develop, test, and deploy while providing a clear path for gradual evolution into microservices if needed.&lt;/p&gt;

&lt;p&gt;Some traits of this subtype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single codebase and deployment unit.&lt;/li&gt;
&lt;li&gt;High cohesion inside the modules and loose coupling between the modules, at least as possible inside a de facto single service.&lt;/li&gt;
&lt;li&gt;In-system communications, no outgoing requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Monolith Migrations Tips&amp;amp;Tricks&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you ever decide to migrate from monolith to microservice here is a few good tips, for you to avoid ending as bad.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Foremost Analyze&lt;/strong&gt; only after carefully analyzing different flows and dependencies you will be able to make correct decision later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrate By Domain Not Layer&lt;/strong&gt; it will make whole process less mind-blowing while also reduce the chances of creating high coupling between components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do It Step By Step&lt;/strong&gt; do not try to migrate everything at once, do it domain by domain, or by any other approach that comes to your mind, maybe migration to "The Good" is a good idea for a start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract Test Right Away&lt;/strong&gt; thought about contract test, and all necessary setup from the start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk, Talk and Talk even more&lt;/strong&gt; be even over-communicative with what you are doing, be 100% sure that all the involved teams know what you are doing and how it affects their work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Clear Ownership&lt;/strong&gt; each service/module need to have clearly assignee owner, there should not be situation that multiple team are responsible for a single service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know When To Stop&lt;/strong&gt; chances are that you are Netflix and probably do not need an architecture of 100+ microservice, think carefully what need to have a separate microservice and what may be merged with some other service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Putting the whole text into a more readable and understandable format:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Monolith Subtype&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Communication&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Coupling&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traditional Monolith (ugly)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single deployment unit.&lt;/td&gt;
&lt;td&gt;In-process communication.&lt;/td&gt;
&lt;td&gt;Tight coupling between different components.&lt;/td&gt;
&lt;td&gt;Grows with the growth of the code base.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed Monolith (bad)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple services – changes in one often require redeploying others.&lt;/td&gt;
&lt;td&gt;Synchronous communication over the network.&lt;/td&gt;
&lt;td&gt;Tight coupling between services.&lt;/td&gt;
&lt;td&gt;High complexity due to distributed nature of deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Modular Monolith (good)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single deployment unit, but internally organized into well-defined modules.&lt;/td&gt;
&lt;td&gt;In-process communication.&lt;/td&gt;
&lt;td&gt;Loosely coupled modules with clear boundaries.&lt;/td&gt;
&lt;td&gt;Moderately complex due to strong modularity and clear responsibilities.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now you know more about different approaches to building and working with monoliths.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/modular-monolith/" rel="noopener noreferrer"&gt;Monolith: The Good, The Bad and The Ugly&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>monolith</category>
      <category>microservices</category>
    </item>
    <item>
      <title>ACID vs BASE: Choosing the Right Transactional Model</title>
      <dc:creator>Bartek Żyliński</dc:creator>
      <pubDate>Sun, 13 Apr 2025 17:35:50 +0000</pubDate>
      <link>https://dev.to/pasksoftware/acid-vs-base-choosing-the-right-transactional-model-4l9k</link>
      <guid>https://dev.to/pasksoftware/acid-vs-base-choosing-the-right-transactional-model-4l9k</guid>
      <description>&lt;p&gt;ACID vs BASE principles are two main approaches to handling transactions. All other approaches are just variations of the two; we can even say that, to a certain degree, BASE is a variation of ACID. Furthermore, some databases may pick to support ACID transactions for part of operations, while not providing the same quarantine for others – &lt;a href="https://www.mongodb.com/resources/basics/databases/acid-transactions" rel="noopener noreferrer"&gt;just like MongoDB here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In today’s text, I will cover the description of both abbreviations, and their use cases, closing with an in-depth summary of the differences between them. For now, let’s say that the biggest difference between the two is: that ACID prioritizes consistency over availability, while BASE prioritizes availability over consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Databases Are Important?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Databases are all around us, just as &lt;a href="https://pasksoftware.com/what-are-distributed-systems/" rel="noopener noreferrer"&gt;distributed systems&lt;/a&gt;. Whether a particular database is more of a relational or non-relational kind does not matter, as we barely see them. We only see our graph of friends on Facebook, our history of messages in Messenger, or “just” our money in a bank account. We do not see all the processing and storage behind them. Essentially, we are focused on some pretty things that are not always stored and processed in such a pretty fashion.&lt;/p&gt;

&lt;p&gt;Transactions, and more specifically the transaction models that we will cover today, are the cornerstone of how our data are being processed and to a degree stored. But what is even a transaction? The definition here is straightforward. From the database perspective, the transaction is any set of operations that can be performed as a single logical unit of work. Besides the classic case of bank transfer between accounts, processing the order, or submitting the review can also be valid transaction examples.&lt;/p&gt;

&lt;p&gt;Transaction models are one of the most important things for database engines. It is a kind of very low level (or high level) API of our database. Following a particular set of principles will result in our database exposing a certain set of features and behaviors. In the end, a particular approach will limit its use cases and make our system more complex than it should be. Knowing the &lt;a href="https://pasksoftware.com/software-engineering-trade-offs/" rel="noopener noreferrer"&gt;trade-offs&lt;/a&gt; of transaction models can be viewed as an advantage, as we can pick the best tool for a job.&lt;/p&gt;

&lt;p&gt;Moreover, while ACID is quite common, and most software engineers have at least heard of it, BASE is quite the opposite – and knowledge is power.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is ACID?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;ACID is an abbreviation for &lt;strong&gt;Atomicity, Consistency, Isolation, Durability&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Atomicity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A transaction must always succeed or fail, with no intermediate results. If a transaction contains any changes of state in multiple tables, then in case of failure none of them should be persisted.&lt;/p&gt;

&lt;p&gt;Following the example from above, assume that we want to process an order for a customer. To do this, we need to perform two operations: update the number of items from the order and create a new order record. If we fail on the second task of the transaction, then the results of the first should be rolled back.&lt;/p&gt;

&lt;p&gt;For example, if after updating the item quantity we fail to create a new order record in our database, then the first part of the transaction should be rolled back – item quantity should remain unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Consistency&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each transaction should preserve all integrity constraints, entity relationships, and business rules present in the database. All these mechanisms should work regardless of the number of concurrent transactions or possible failures that may occur in the meantime.&lt;/p&gt;

&lt;p&gt;It means that any transaction should move the database from one valid version of state to the other.&lt;/p&gt;

&lt;p&gt;For example, before the order creation, the quantity of items was: A=7, B=10, and the customer ordered 5 pieces of A and 3 pieces of B. Then after the transaction, we should have 2 pieces of A and 7 pieces of B in store. The total amount after the transaction should be the same as before.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Isolation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In a concurrent environment, transactions should not interfere with one another. It means that changes from one transaction will not be visible to another transaction before it commits. Such an approach gives us the impression that the transactions are executed one by one, while underneath they are executed simultaneously.&lt;/p&gt;

&lt;p&gt;Additionally, there is one more thing that needs to be mentioned here: &lt;strong&gt;Isolation Level&lt;/strong&gt;. It is a database-wide setting that describes how much a transaction is affected by other transactions. In most of the databases, we can tune up (or down) the Isolation level. Typically, the higher we go up with Isolations, the more we degrade the database performance, and vice versa.&lt;/p&gt;

&lt;p&gt;The official SQL documentation describes four different Isolation Levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt; – Transaction can view data changed but yet uncommitted by other transactions. In such a case, when rollback occurs, we might use data that does not exist in our database. Usually, you should not use this Isolation Level. It only makes sense when you are querying a dataset that will not change at all in any way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;READ COMMITTED&lt;/strong&gt; – Transactions can only read data that are committed at the moment they are read. It offers a good balance between consistency and performance in most of the use cases. Thus, it is the most common default Isolation Level used in multiple databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REPEATABLE READ&lt;/strong&gt; – This Isolation Level aims to address the issue of different read values of the same queries within a single transaction. It is an ideal Isolation Level for read-only transactions, as it guarantees that if the row is read twice in the same transaction, it will return the same value each time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SERIALIZABLE&lt;/strong&gt; – It is the highest Isolation Level, here all the transactions are completely isolated from one another, supporting the most demanding consistency guarantees. However, it effectively makes database reads serial and may produce more transaction retry errors coming from interference between them. In most cases, the combination of these two factors results in a significant performance decrease.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of the Levels after the &lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt; aims to cover specific &lt;strong&gt;Read phenomena&lt;/strong&gt; , with the higher Levels also covering the issues of predecessors.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Isolation Level&lt;/th&gt;
&lt;th&gt;Read Phenomena&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;Dirty Reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPEATABLE READ&lt;/td&gt;
&lt;td&gt;Dirty Reads, Repeatable Read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;Dirty Reads, Repeatable Read, Phantom Read&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The detailed description of these problems is kind of out of the scope of this text, but you can read more &lt;a href="https://en.wikipedia.org/wiki/Isolation_(database_systems#Read_Phenomena)" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Durability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;After the commit of the transaction, its results are guaranteed to be permanent. Even in the case of system failure, crash or power loss, there should be no after-commit data loss. Usually, it is done by saving the results on some form of persistent storage; in most cases, it is a plain old hard drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is BASE?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;BASE is an abbreviation for &lt;strong&gt;Basically Available, Soft State, and Eventually Consistent&lt;/strong&gt;. The acronym serves to highlight their difference. In chemistry, &lt;a href="https://www.diffen.com/difference/Acid_vs_Base" rel="noopener noreferrer"&gt;ACID is the opposite of BASE&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Basically Available&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In a concurrent environment, the database guarantees availability. It will try to respond to the request even with somewhat stale or incomplete data. What this means is that in the case of a sudden spike of requests, it may choose to focus on handling read requests while slowing down updates and inserts.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Soft State&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The state of the system may change even without immediate reason, due to the approach built over the eventual consistency model. When multiple applications update databases, the particular record (or records) is in an intermediate state. The final consistent state will be calculated when all transactions for a particular record are complete. All intermediate states are also visible to all interloping reads or writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Eventually Consistent&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The database will eventually reach a consistent state when no more updates will be present. At some random point, we may see some inconsistencies, but they should be resolved in a longer time span. &lt;strong&gt;Consistency&lt;/strong&gt; is not guaranteed at a transaction level.&lt;/p&gt;

&lt;p&gt;For example, in the case of a geo-distributed database, when some record is updated, the new state may not initially be visible to users in different regions due to network latency. However, after some time, the new state will be propagated to all nodes and thus the change becomes visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;ACID vs BASE&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Data Integrity&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ACID&lt;/strong&gt; properties provide a more strict guarantee as to the consistency of our state. Random occurrences of some data inconsistency are very rare. Also, the potential chances for data loss are relatively low. Though they still exist, it is just quite hard to cause them – at least without some bad luck.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qwll3hfp0f5zz6ij216.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qwll3hfp0f5zz6ij216.jpg" alt="Wrong DB selected" width="460" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BASE&lt;/strong&gt; , on the other hand provides, little to no guarantees as to the state of our database. We only know that at some point in the future, it will be consistent, but when it will be – nobody knows. All intermediate state changes are still visible and may impact other queries. The BASE also does not guarantee the data durability by principle. Without such a guarantee, the event of partial or total data loss is more likely to happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Integration Complexity&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ACID&lt;/strong&gt; , because of its high consistency guarantees, it takes a lot of burden off of software engineers’; shoulders. With ACID, we usually do not have to worry too much about an inconsistency between transactions, “seemingly random” data changes, and or losing part of our data on the fly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BASE&lt;/strong&gt; in this case, we have to cover most of such cases by ourselves. We have to know that some inconsistency may – and thus will – occur. Thus, we must implement their resolution accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Performance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ACID&lt;/strong&gt; -complaints databases usually use locks to synchronize access to particular resources in a concurrent environment. As the transactions on conflicting records are processed in a strict order, you should be ready for some delays in transaction resolution. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BASE&lt;/strong&gt; -compliant systems are somewhat simpler – there are no locks. The database is synchronizing the operations eventually without hard time guarantees. Nevertheless, the time of transaction resolution may also become quite long.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Scalability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By principle, the &lt;strong&gt;BASE&lt;/strong&gt; compliant database scale is noticeably better than &lt;strong&gt;ACID&lt;/strong&gt; ones – mostly due to a more relaxed consistency model. In general, most BASE databases are designed to support horizontal scaling, while ACID ones are limited by vertical scaling with the possibility for read replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;BASE vs ACID In A Single Database&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Such behavior is virtually impossible due to the vastly different consistency models of both approaches. Additionally, in the case of a network partition, we can choose to be either available or consistent; we cannot be both at the same time.&lt;/p&gt;

&lt;p&gt;Despite this fact, there are a couple of NoSQL databases that try to mix both paradigms. Thus, they expose ACID properties in certain parts, or for certain features. This list is quite long, so I will not put the whole, but it includes databases like &lt;a href="https://www.mongodb.com/resources/basics/databases/acid-transactions" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt;, &lt;a href="https://thenewstack.io/an-apache-cassandra-breakthrough-acid-transactions-at-scale/" rel="noopener noreferrer"&gt;Cassandra&lt;/a&gt; (ACID support for single row operations), and &lt;a href="https://docs.couchbase.com/sdk-extensions/distributed-acid-transactions.html" rel="noopener noreferrer"&gt;Couchbase&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As always, the summary is quick and simple — just look below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ACID&lt;/th&gt;
&lt;th&gt;BASE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Integrity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High consistency and durability&lt;/td&gt;
&lt;td&gt;Eventual consistency, no durability guarantee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lower, most of the inconsistencies are handled on the DB side.&lt;/td&gt;
&lt;td&gt;Higher, we have to be aware of inconsistencies and their impact.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;May degrade rapidly in concurrent environment&lt;/td&gt;
&lt;td&gt;Should handle more load without performance degradation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vertical scaling with read replicas&lt;/td&gt;
&lt;td&gt;Horizontal scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Please keep in mind that this comparison is only focused on the theoretical difference between both approaches. It aims to deepen your knowledge of both of them.&lt;/p&gt;

&lt;p&gt;When choosing an exact database, I would recommend focusing more on a product-to-product comparison. Most of the features and behaviors may differ depending on the exact database implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you for your time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blog &lt;a href="https://pasksoftware.com/acid-vs-base/" rel="noopener noreferrer"&gt;ACID vs BASE: Choosing the Right Transactional Model&lt;/a&gt; from &lt;a href="https://pasksoftware.com" rel="noopener noreferrer"&gt;Pask Software&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>acid</category>
      <category>base</category>
      <category>database</category>
    </item>
  </channel>
</rss>
