<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Dispatches — The Utsab Dahal Times</title>
    <link>https://dahalutsab.com.np/</link>
    <description>Notes on Spring Boot, backend architecture and API design, from Utsab Dahal.</description>
    <language>en</language>
    <copyright>2026 Utsab Dahal</copyright>
    <managingEditor>info@dahalutsab.com.np (Utsab Dahal)</managingEditor>
    <webMaster>info@dahalutsab.com.np (Utsab Dahal)</webMaster>
    <lastBuildDate>Mon, 03 Aug 2026 09:00:00 GMT</lastBuildDate>
    <atom:link href="https://dahalutsab.com.np/feed.xml" rel="self" type="application/rss+xml"/>
    <image>
      <url>https://dahalutsab.com.np/og-card.png</url>
      <title>Dispatches — The Utsab Dahal Times</title>
      <link>https://dahalutsab.com.np/</link>
    </image>
    <item>
      <title>Letting Anonymous Users Act: Participant Tokens in Spring Security</title>
      <link>https://dahalutsab.com.np/dispatches/anonymous-participant-tokens-spring-security</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/anonymous-participant-tokens-spring-security</guid>
      <pubDate>Mon, 03 Aug 2026 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>Spring Security</category>
      <category>Spring Security</category>
      <category>JWT</category>
      <category>OAuth2</category>
      <category>Authentication</category>
      <category>Architecture</category>
      <description>How to enforce one vote per person, per-session scoping and moderation rules for users who never create an account — a second JWT issuer, a published JWK set, and a unique constraint on an identity that is not a person.</description>
      <content:encoded><![CDATA[<p>Most authentication tutorials answer the question &quot;who is this person?&quot;. This one answers a harder question: &quot;how do I let someone act, safely, when I have deliberately decided never to find out who they are?&quot;</p>
<h2 id="the-problem">The problem</h2>
<p>I was building Interacta, a live audience-engagement platform. A speaker runs a session, puts a join code on the screen, and the room asks questions, upvotes them and votes in polls — in real time, from their phones.</p>
<p>The product constraint was simple and non-negotiable: <strong>the audience never creates an account</strong>. Nobody sitting in a conference hall is going to register, verify an email and pick a password in order to ask one question. If the join flow is longer than scanning a QR code, the feature is dead.</p>
<p>But &quot;no account&quot; is not the same as &quot;no rules&quot;. The backend still had to enforce:</p>
<ul><li>One upvote per person per question. Not one per click.</li><li>One ballot per person per poll.</li><li>A question submitted in session A must never touch session B.</li><li>A listener must not be able to read the moderation queue, which contains rejected questions.</li><li>All of this without storing a single piece of personally identifying information about the listener.</li></ul>
<p>The last two lines are the interesting part. Authorization normally hangs off an identity. Take the identity away and most of the usual toolkit stops working.</p>
<h2 id="why-the-obvious-answers-do-not-work">Why the obvious answers do not work</h2>
<p><strong>Spring Security anonymous authentication.</strong> <code>ROLE_ANONYMOUS</code> is the same principal for every unauthenticated caller. It tells you nothing about <em>which</em> anonymous caller you are talking to, so it cannot enforce one vote per person.</p>
<p><strong>A session cookie.</strong> This works, right up until you remember that the point of a stateless service is not keeping server-side session state, and that the frontend, the gateway and five services would all need to agree about the cookie. It also fails the moment a listener has third-party cookies locked down.</p>
<p><strong>Rate limiting by IP.</strong> A conference hall is one NAT. A campus is one NAT. Half the audience shares an address, so IP-based deduplication either lets one person vote fifty times or lets one person block everyone else in the room.</p>
<p><strong>A client-generated ID in localStorage.</strong> This is the one that looks like it works. The client makes up a UUID, sends it as a header, the server counts votes against it. It is also trivially forgeable: open devtools, change the string, vote again. Anything the client can invent, the client can invent twice.</p>
<p>The common failure in all four is the same. <strong>A voter identity has to be issued by the server and unforgeable by the client — it just does not have to be a person.</strong></p>
<h2 id="the-move-a-second-token-issuer">The move: a second token issuer</h2>
<p>The answer was to stop thinking of the listener as unauthenticated. A listener <em>is</em> authenticated. They are simply authenticated as a random number with a very short lease.</p>
<p>When someone joins with a code, <code>session-service</code> mints them a signed JWT:</p>
<pre><code class="language-java">@Service
public class ParticipantTokenService {

    private final JWSSigner signer;
    private final String issuer;
    private final Duration ttl;
    private final String keyId;

    public ParticipantToken mint(Session session) {
        Instant now = Instant.now();
        String participantId = UUID.randomUUID().toString();

        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .issuer(issuer)                       // NOT keycloak
                .subject(participantId)               // a random number, not a person
                .claim(&quot;sessionId&quot;, session.getId().toString())
                .claim(&quot;joinCode&quot;, session.getJoinCode())
                .claim(&quot;role&quot;, &quot;PARTICIPANT&quot;)
                .jwtID(UUID.randomUUID().toString())
                .issueTime(Date.from(now))
                .expirationTime(Date.from(now.plus(ttl)))
                .build();

        SignedJWT jwt = new SignedJWT(
                new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(keyId).build(),
                claims);

        jwt.sign(signer);
        return new ParticipantToken(jwt.serialize(), ttl.toSeconds(), participantId);
    }
}</code></pre>
<p>Read the claim set again and notice what is absent. No email. No name. No username. No device fingerprint. The <code>sub</code> is a UUID generated at join time and never written next to anything that identifies a human being.</p>
<p>What the token <em>does</em> carry is authority: it is signed with RS256 by a key only <code>session-service</code> holds, so the client cannot mint one, and cannot edit the <code>sessionId</code> in the one they were given.</p>
<p>The join endpoint is public, and that is fine — it is public in the same way a door is public:</p>
<pre><code class="language-java">@PostMapping(&quot;/api/v1/sessions/join/{joinCode}/participant&quot;)
public ResponseEntity&lt;ParticipantTokenResponse&gt; join(@PathVariable String joinCode) {
    Session session = sessions.findByJoinCode(joinCode)
            .orElseThrow(() -&gt; new NotFoundException(&quot;No session for that code&quot;));

    if (session.getStatus() == SessionStatus.ENDED) {
        throw new ConflictException(&quot;That session has ended&quot;);
    }

    return ResponseEntity.ok(tokenService.mint(session));
}</code></pre>
<h2 id="publishing-the-keys">Publishing the keys</h2>
<p>A token nobody else can verify is useless. Five services need to check these tokens, and none of them should be handed a signing secret to do it — that would turn every service into a service that can forge participant tokens.</p>
<p>So <code>session-service</code> does what any OIDC provider does: it publishes the public half as a JWK Set.</p>
<pre><code class="language-java">@RestController
public class JwksController {

    private final JWKSet publicKeys;

    @GetMapping(path = &quot;/oauth2/jwks&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
    public Map&lt;String, Object&gt; jwks() {
        return publicKeys.toJSONObject();   // public parameters only
    }
}</code></pre>
<p>That single endpoint is what turns an internal implementation detail into a standard. Every consumer now uses the same Spring Security machinery it already uses for Keycloak, pointed at a different URL.</p>
<h2 id="two-issuers-one-resource-server">Two issuers, one resource server</h2>
<p>Here is the part that surprised me: Spring Security supports this out of the box, and has since 5.3. You do not need a custom filter.</p>
<p><code>JwtIssuerAuthenticationManagerResolver</code> reads the <code>iss</code> claim before validating anything else, and routes the token to the right <code>AuthenticationManager</code>:</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class ResourceServerConfig {

    @Value(&quot;${keycloak.issuer}&quot;)      private String keycloakIssuer;
    @Value(&quot;${keycloak.jwk-set-uri}&quot;) private String keycloakJwks;
    @Value(&quot;${participant.issuer}&quot;)   private String participantIssuer;
    @Value(&quot;${participant.jwk-set-uri}&quot;) private String participantJwks;

    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .csrf(CsrfConfigurer::disable)
            .sessionManagement(s -&gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -&gt; auth
                .requestMatchers(HttpMethod.GET, &quot;/actuator/health&quot;).permitAll()
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth -&gt;
                oauth.authenticationManagerResolver(issuerResolver()))
            .build();
    }

    private AuthenticationManagerResolver&lt;HttpServletRequest&gt; issuerResolver() {
        Map&lt;String, AuthenticationManager&gt; managers = Map.of(
            keycloakIssuer,    manager(keycloakDecoder()),
            participantIssuer, manager(participantDecoder()));

        return new JwtIssuerAuthenticationManagerResolver(managers::get);
    }

    private AuthenticationManager manager(JwtDecoder decoder) {
        JwtAuthenticationProvider provider = new JwtAuthenticationProvider(decoder);
        provider.setJwtAuthenticationConverter(converterFor(decoder));
        return provider::authenticate;
    }
}</code></pre>
<p>Each issuer gets its own decoder and its own authority converter. Keycloak realm roles become <code>ROLE_HOST</code>, <code>ROLE_ADMIN</code> and so on; a participant token becomes exactly one authority:</p>
<pre><code class="language-java">public class ParticipantAuthorityConverter
        implements Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt; {

    @Override
    public Collection&lt;GrantedAuthority&gt; convert(Jwt jwt) {
        // A participant token grants one thing and only one thing.
        return List.of(new SimpleGrantedAuthority(&quot;ROLE_PARTICIPANT&quot;));
    }
}</code></pre>
<p>The staff side of the application does not change at all. It never learns that a second issuer exists.</p>
<h2 id="the-check-that-actually-matters">The check that actually matters</h2>
<p><code>ROLE_PARTICIPANT</code> is nearly worthless on its own. It says &quot;this caller joined <em>a</em> session&quot;, not &quot;this caller joined <em>this</em> session&quot;. Without a second check, a token minted for a session you were invited to would let you upvote in a session you were not.</p>
<p>So every handler that touches a session asserts that the token's <code>sessionId</code> claim matches the session being acted on. I wrapped it up in a <code>CallerContext</code> so it could not be forgotten:</p>
<pre><code class="language-java">@Component
public class CallerContext {

    /** The participant id, or the Keycloak subject — whoever is asking. */
    public String voterId(Jwt jwt) {
        return jwt.getSubject();
    }

    public boolean isParticipantOf(Jwt jwt, UUID sessionId) {
        return &quot;PARTICIPANT&quot;.equals(jwt.getClaimAsString(&quot;role&quot;))
            &amp;&amp; sessionId.toString().equals(jwt.getClaimAsString(&quot;sessionId&quot;));
    }

    /** Throws unless the caller hosts this session, or is scoped to it. */
    public void requireAccessTo(Jwt jwt, SessionRef session) {
        if (isParticipantOf(jwt, session.getId())) return;
        if (session.getHostId().equals(jwt.getSubject())) return;
        throw new ForbiddenException(&quot;Not your session&quot;);
    }
}</code></pre>
<p>Two rules, applied identically in every service: <em>is this caller the host of this session, or a participant scoped to it?</em> Everything else falls out of that.</p>
<h2 id="one-vote-per-person-without-knowing-the-person">One vote per person, without knowing the person</h2>
<p>This is where the design pays for itself. Because the participant id is server-issued and unforgeable, it can go straight into a unique constraint:</p>
<pre><code class="language-java">@Entity
@Table(name = &quot;question_upvotes&quot;,
       uniqueConstraints = @UniqueConstraint(
           name = &quot;uk_upvote_question_voter&quot;,
           columnNames = {&quot;question_id&quot;, &quot;voter_id&quot;}))
public class QuestionUpvote {

    @Id @GeneratedValue
    private UUID id;

    @Column(name = &quot;question_id&quot;, nullable = false)
    private UUID questionId;

    /** The participant id from the token. Not a user, not a person. */
    @Column(name = &quot;voter_id&quot;, nullable = false, length = 64)
    private String voterId;

    @CreationTimestamp
    private Instant createdAt;
}</code></pre>
<p>The database enforces the rule. Not the UI, not a service-layer <code>if</code>, not a client-side disabled button — the schema. A retry, a double-tap, a replayed request and a determined person with curl all hit the same constraint.</p>
<p>Pre-check before the insert so an honest duplicate gets an honest answer instead of a 500:</p>
<pre><code class="language-java">@Transactional
public void upvote(UUID questionId, String voterId) {
    if (upvotes.existsByQuestionIdAndVoterId(questionId, voterId)) {
        throw new ConflictException(&quot;Already counted&quot;);
    }
    upvotes.save(new QuestionUpvote(questionId, voterId));
    events.publish(new QuestionUpvotedEvent(questionId));
}</code></pre>
<p>Anonymity and accountability turn out not to be opposites. You just have to be precise about which one you actually needed.</p>
<h2 id="what-you-give-up">What you give up</h2>
<p>No design is free, and pretending otherwise is how people get burned.</p>
<p><strong>Revocation is gone.</strong> A stateless JWT is valid until it expires. There is no session table to delete a row from. The mitigation is a short TTL — six hours by default, roughly the length of an event — and accepting that a badly behaved participant can be silenced at the <em>content</em> level rather than the <em>token</em> level. If you need true revocation, you need a deny-list, and you have just reintroduced the state you were avoiding.</p>
<p><strong>Restart invalidates everything, if your key is ephemeral.</strong> In development the signing key is generated at boot, so every restart orphans every outstanding token. That is fine locally and catastrophic in production. Load a stable key from configuration before you deploy. I left a note in the README about this specifically because it is the kind of thing that is obvious right up until it is 2am.</p>
<p><strong>No identity across devices.</strong> A participant token is bound to the browser that requested it. Switch phones and you are a new person with new votes. For a live session that is acceptable. For anything that needs continuity, it is not.</p>
<p><strong>Clock skew matters.</strong> Two services validating <code>exp</code> against drifting clocks will disagree about whether a token is alive. Run NTP.</p>
<h2 id="when-to-reach-for-this">When to reach for this</h2>
<p>The pattern generalises past live events. It fits anywhere you need per-actor rules without per-actor accounts:</p>
<ul><li>Poll or survey links where one response per recipient matters.</li><li>Guest checkout that has to survive several requests.</li><li>A support chat widget where the visitor is scoped to one conversation.</li><li>Any QR-code-driven interaction: table ordering, event check-in, classroom feedback.</li></ul>
<p>The recipe is always the same four steps. Issue a signed token from the server. Publish the public key. Route by issuer so your existing security config keeps working. Put the server-issued id in a unique constraint.</p>
<p>The insight worth keeping is that authentication and identification are not the same thing. You can prove a caller is <em>the same caller as last time</em> without ever learning, or storing, who they are — and for a great many features, that is all the proof the rules actually needed.</p>]]></content:encoded>
    </item>
    <item>
      <title>Your API Gateway Cannot Proxy Your WebSocket</title>
      <link>https://dahalutsab.com.np/dispatches/spring-cloud-gateway-cannot-proxy-websockets</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/spring-cloud-gateway-cannot-proxy-websockets</guid>
      <pubDate>Mon, 27 Jul 2026 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>System Design</category>
      <category>Spring Cloud Gateway</category>
      <category>WebSocket</category>
      <category>STOMP</category>
      <category>Microservices</category>
      <category>Architecture</category>
      <description>Spring Cloud Gateway Server WebMVC cannot proxy a WebSocket upgrade, and it fails without saying so. What to do instead, how to authenticate a socket a browser cannot put a header on, and the SockJS origin trap that logs nothing at all.</description>
      <content:encoded><![CDATA[<p>Every REST route worked. Health checks were green. Then I opened the page that needed live updates and the browser sat there, retrying, forever.</p>
<p>This is the writeup I wanted when that happened.</p>
<h2 id="the-symptom">The symptom</h2>
<p>The architecture was ordinary. Spring Cloud Gateway on <code>:8090</code> in front of six Spring Boot services, and one of those services holding the WebSocket connections for realtime fan-out. The frontend was told to open <code>wss://host/ws</code> and let the gateway route it like everything else.</p>
<p>What you get instead, depending on your exact configuration:</p>
<ul><li>A <code>404</code> on the upgrade request, even though the route is plainly declared.</li><li>A <code>400 Bad Request</code> from the gateway, no logs at all in the target service.</li><li>SockJS silently giving up on the WebSocket transport and falling back to XHR streaming — which appears to work, badly, until it does not work at all.</li><li>On a good day, nothing whatsoever in any log, anywhere.</li></ul>
<p>I lost most of an afternoon to the last one, checking route order and CORS, because a route that fails silently looks exactly like a route you got wrong.</p>
<h2 id="the-cause">The cause</h2>
<p>Spring Cloud Gateway ships in two flavours, and this is not obvious from the starter names.</p>
<p><strong>Spring Cloud Gateway Server WebFlux</strong> is the original: reactive, running on Netty. It proxies WebSockets fine, because a non-blocking runtime can hold thousands of long-lived upgraded connections without holding thousands of threads.</p>
<p><strong>Spring Cloud Gateway Server WebMVC</strong> is the newer, blocking, Servlet-stack variant. It exists so you can run a gateway without dragging Reactor and Netty into a codebase that is otherwise plain Spring MVC — which is a genuinely good reason to pick it.</p>
<p>It also cannot proxy a WebSocket upgrade. This is a known and open limitation, tracked as [spring-cloud-gateway#3442](https://github.com/spring-cloud/spring-cloud-gateway/issues/3442). Not a bug in your route definition. Not a missing filter. The Servlet-based gateway does not implement the upgrade path.</p>
<p>That is worth saying plainly because the error messages point everywhere except at the answer:</p>
<p><strong>If your gateway is the MVC variant, no amount of configuration will make `/ws` work through it.</strong></p>
<h2 id="three-ways-out">Three ways out</h2>
<p><strong>Switch to the reactive gateway.</strong> The upgrade proxying works. The cost is that Reactor and Netty are now in your dependency tree, your filters are written against a reactive API, and any blocking code in a custom filter becomes a production incident rather than a code review comment. If your team is not already fluent in reactive Spring, this is a large bill for one feature.</p>
<p><strong>Put the realtime service beside the gateway, not behind it.</strong> Clients open <code>/ws</code> directly against the realtime service. The gateway keeps <code>/api</code>. Two origins — unless something in front collapses them back into one, which is the next section.</p>
<p><strong>Give realtime its own subdomain.</strong> <code>wss://realtime.example.com</code>. Clean, and now you own a second certificate, a second CORS allowlist, and cookies that do not span both hosts.</p>
<p>I took the second option. Here is what it actually costs, because &quot;just bypass the gateway&quot; is where most writeups stop and where the real work starts.</p>
<h2 id="collapsing-the-origins">Collapsing the origins</h2>
<p>Two ports is fine on localhost and miserable everywhere else: CORS preflights on every call, a second certificate, and browsers that will not expose the Web Crypto API — which OIDC PKCE needs — outside a secure context.</p>
<p>A reverse proxy in front of both fixes it. This is the entire Caddy config:</p>
<pre><code>host.local {
    encode gzip

    handle /api/* {
        reverse_proxy localhost:8090
    }

    handle /ws/* {
        reverse_proxy localhost:8085
    }

    handle {
        reverse_proxy localhost:4200
    }
}</code></pre>
<p>Caddy terminates TLS, and the browser now sees one origin serving the app, the REST API and the WebSocket. CORS stops being a problem because there is nothing cross-origin left. In production the same shape works with nginx or an ingress; the routing rule is the same three prefixes.</p>
<p>The architectural point is worth keeping: <strong>the gateway is not the only thing that can present a single origin.</strong> It was doing two jobs — routing and origin unification — and only one of them actually had to be its.</p>
<h2 id="now-authenticate-it">Now authenticate it</h2>
<p>Bypassing the gateway raises a question the gateway was quietly answering: who is allowed to open this socket?</p>
<p>And here you meet the second wall. <strong>A browser cannot attach an `Authorization` header to a WebSocket upgrade.</strong> The <code>WebSocket</code> constructor takes a URL and a subprotocol list. That is all. There is nowhere to put a bearer token.</p>
<p>The usual workarounds are all bad. A token in the query string ends up in access logs, in <code>Referer</code> headers and in browser history. A cookie reintroduces CSRF surface and cross-origin cookie rules. A subprotocol field abuses a header that means something else.</p>
<p>The clean answer with STOMP: do not authenticate the transport. Authenticate the first frame.</p>
<p>STOMP has its own <code>CONNECT</code> frame with arbitrary headers, sent immediately after the socket opens. Put the token there, and reject the connection in a <code>ChannelInterceptor</code> before any message is dispatched:</p>
<pre><code class="language-java">@Component
public class StompAuthChannelInterceptor implements ChannelInterceptor {

    private final AuthenticationManagerResolver&lt;String&gt; byIssuer;

    @Override
    public Message&lt;?&gt; preSend(Message&lt;?&gt; message, MessageChannel channel) {
        StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        if (accessor == null) return message;

        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String bearer = accessor.getFirstNativeHeader(&quot;Authorization&quot;);
            if (bearer == null || !bearer.startsWith(&quot;Bearer &quot;)) {
                throw new MessagingException(&quot;No credentials on CONNECT&quot;);
            }
            Authentication auth = authenticate(bearer.substring(7));
            accessor.setUser(auth);          // sticks for the whole session
            return message;
        }

        if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) {
            authorizeDestination(accessor.getUser(), accessor.getDestination());
        }

        if (StompCommand.SEND.equals(accessor.getCommand())) {
            // Broadcast-only: every action goes through REST, so a client
            // that tries to publish is either confused or probing.
            throw new MessagingException(&quot;This channel does not accept SEND&quot;);
        }

        return message;
    }
}</code></pre>
<p>Three things are happening there, and each one earned its place.</p>
<p><strong>Authenticate on CONNECT.</strong> The principal set with <code>accessor.setUser()</code> stays attached for the life of the session, so later frames do not re-present credentials.</p>
<p><strong>Authorize on SUBSCRIBE.</strong> This is the check people forget. Destinations look like <code>/topic/sessions/{id}/questions</code>, and without a check, any authenticated client can subscribe to <em>any</em> session's topics — including <code>/moderation</code>, which carries rejected content and is staff-only. The token has to be scoped to the session named in the destination.</p>
<p><strong>Refuse SEND outright.</strong> The socket is a broadcast channel. Every action goes through REST, where the normal authorization already lives. Accepting SEND would create a second, weaker way into the domain.</p>
<h2 id="the-origin-trap">The origin trap</h2>
<p>One more, because it cost me an hour and produces zero useful logs.</p>
<p><strong>SockJS sends an `Origin` header on every request, including same-origin ones.</strong> If your allowed-origins list does not match the browser's origin <em>exactly</em> — scheme, host and port — the handshake is rejected before your interceptor ever runs. You see no CONNECT, no error, no clue.</p>
<pre><code class="language-java">registry.addEndpoint(&quot;/ws&quot;)
        .setAllowedOrigins(allowedOrigins)   // must match exactly
        .withSockJS();</code></pre>
<p>Two commands worth keeping. First, has any frame ever arrived?</p>
<pre><code class="language-bash">docker logs realtime-service | grep -c &quot;STOMP CONNECT authenticated&quot;</code></pre>
<p>Zero means the transport never carried a frame, and the cause is almost always the origin. Then ask the endpoint directly:</p>
<pre><code class="language-bash">curl -sk https://host.local/ws/info -H &quot;Origin: https://host.local&quot;
# good:  {&quot;entropy&quot;:123456,&quot;websocket&quot;:true,...}
# bad:   Invalid CORS request</code></pre>
<p>That second command turns an invisible failure into a one-line answer.</p>
<h2 id="what-to-put-in-the-frames">What to put in the frames</h2>
<p>A design note that has nothing to do with transports and everything to do with not leaking data.</p>
<p>It is tempting to broadcast the whole object — the question with its text, the poll with its tally. It is also wrong, because a topic has many subscribers and they do not all have the same rights. A poll with results hidden while voting is open must not have its tally on the wire just because the UI would not have drawn it.</p>
<p>So the frames are deliberately thin:</p>
<pre><code class="language-java">public record PollFrame(String type, UUID pollId, UUID sessionId) { }</code></pre>
<p><code>LAUNCHED</code>, an id, nothing else. The client re-fetches over REST, and the server decides what <em>that particular viewer</em> is allowed to see, per request, with the authorization it already has. The socket says <em>something changed</em>. The API says <em>what you may know about it</em>.</p>
<p>This also makes the frames tiny, which matters when one popular session fans out to a few hundred subscribers.</p>
<h2 id="scaling-past-one-node">Scaling past one node</h2>
<p>The in-memory broker — <code>enableSimpleBroker()</code> — keeps subscriptions in the heap of one process. Two instances behind a load balancer means a message published on node A never reaches a client connected to node B, and the bug looks like &quot;realtime works for some users&quot;.</p>
<p>The fix is a broker relay, RabbitMQ or ActiveMQ, so the broadcast goes to a broker both nodes are attached to. Do not do this until you actually run two nodes, but know that it is the step you are deferring.</p>
<h2 id="the-short-version">The short version</h2>
<ul><li>Spring Cloud Gateway Server <strong>WebMVC</strong> cannot proxy WebSockets. Check which starter you have before you debug anything else.</li><li>Either go reactive, or let clients reach the realtime service directly and collapse the origins with a reverse proxy in front of both.</li><li>Browsers cannot put a header on a WebSocket upgrade. Authenticate on the STOMP <code>CONNECT</code> frame, authorize on <code>SUBSCRIBE</code>, and refuse <code>SEND</code>.</li><li>SockJS sends <code>Origin</code> on same-origin requests too. Match it exactly, or nothing works and nothing is logged.</li><li>Broadcast that something changed, not what changed. Let the API decide what each viewer may see.</li></ul>
<p>None of this is exotic. It is just a set of things that are obvious afterwards, and completely invisible while you are staring at a route definition that looks perfectly correct.</p>]]></content:encoded>
    </item>
    <item>
      <title>Building Scalable REST APIs with Spring Boot</title>
      <link>https://dahalutsab.com.np/dispatches/building-scalable-rest-apis-spring-boot</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/building-scalable-rest-apis-spring-boot</guid>
      <pubDate>Mon, 15 Jan 2024 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>Spring Boot</category>
      <category>Spring Boot</category>
      <category>REST API</category>
      <category>Java</category>
      <category>Backend</category>
      <description>A comprehensive guide to designing and implementing RESTful APIs using Spring Boot, covering best practices for authentication, validation, and error handling.</description>
      <content:encoded><![CDATA[<p>REST APIs are the backbone of modern web applications. In this comprehensive guide, we'll explore how to build scalable, maintainable REST APIs using Spring Boot.</p>
<h2 id="introduction">Introduction</h2>
<p>Spring Boot has revolutionized Java development by providing a convention-over-configuration approach that allows developers to quickly create production-ready applications. When it comes to building REST APIs, Spring Boot offers powerful features that make development both efficient and enjoyable.</p>
<h3 id="why-spring-boot-for-rest-apis">Why Spring Boot for REST APIs?</h3>
<ul><li><strong>Auto-configuration</strong>: Minimal setup required</li><li><strong>Embedded servers</strong>: No need for external application servers</li><li><strong>Production-ready features</strong>: Health checks, metrics, and monitoring</li><li><strong>Rich ecosystem</strong>: Extensive library support</li><li><strong>Developer experience</strong>: Hot reloading and excellent tooling</li></ul>
<h2 id="project-setup">Project Setup</h2>
<p>Let's start by creating a new Spring Boot project with the necessary dependencies.</p>
<h3 id="maven-dependencies">Maven Dependencies</h3>
<pre><code class="language-xml">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;mysql&lt;/groupId&gt;
        &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<h3 id="application-properties">Application Properties</h3>
<p>Configure your database connection and other settings:</p>
<pre><code class="language-properties">spring.datasource.url=jdbc:mysql://localhost:3306/api_db
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true</code></pre>
<h2 id="creating-entities">Creating Entities</h2>
<p>Let's create a User entity as an example:</p>
<pre><code class="language-java">@Entity
@Table(name = &quot;users&quot;)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    @Email
    private String email;
    
    @Column(nullable = false)
    @Size(min = 2, max = 50)
    private String firstName;
    
    @Column(nullable = false)
    @Size(min = 2, max = 50)
    private String lastName;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
    
    // Constructors, getters, and setters
}</code></pre>
<h3 id="repository-layer">Repository Layer</h3>
<p>Create a repository interface:</p>
<pre><code class="language-java">@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByEmail(String email);
    List&lt;User&gt; findByFirstNameContainingIgnoreCase(String firstName);
}</code></pre>
<h2 id="building-controllers">Building Controllers</h2>
<p>Now let's create a REST controller:</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/api/users&quot;)
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public ResponseEntity&lt;List&lt;User&gt;&gt; getAllUsers() {
        List&lt;User&gt; users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
    
    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;User&gt; getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    public ResponseEntity&lt;User&gt; createUser(@Valid @RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    @PutMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;User&gt; updateUser(
            @PathVariable Long id, 
            @Valid @RequestBody User user) {
        User updatedUser = userService.updateUser(id, user);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Void&gt; deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}</code></pre>
<h3 id="service-layer">Service Layer</h3>
<p>Implement the business logic:</p>
<pre><code class="language-java">@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List&lt;User&gt; getAllUsers() {
        return userRepository.findAll();
    }
    
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -&gt; new UserNotFoundException(&quot;User not found with id: &quot; + id));
    }
    
    public User createUser(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new EmailAlreadyExistsException(&quot;Email already exists: &quot; + user.getEmail());
        }
        return userRepository.save(user);
    }
    
    public User updateUser(Long id, User userDetails) {
        User user = getUserById(id);
        user.setFirstName(userDetails.getFirstName());
        user.setLastName(userDetails.getLastName());
        user.setEmail(userDetails.getEmail());
        return userRepository.save(user);
    }
    
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }
}</code></pre>
<h2 id="error-handling">Error Handling</h2>
<p>Implement global exception handling:</p>
<pre><code class="language-java">@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity&lt;ErrorResponse&gt; handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.NOT_FOUND.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(EmailAlreadyExistsException.class)
    public ResponseEntity&lt;ErrorResponse&gt; handleEmailAlreadyExists(EmailAlreadyExistsException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.CONFLICT.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity&lt;ErrorResponse&gt; handleValidationErrors(MethodArgumentNotValidException ex) {
        List&lt;String&gt; errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
            
        ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            &quot;Validation failed: &quot; + String.join(&quot;, &quot;, errors),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}</code></pre>
<h2 id="testing">Testing</h2>
<p>Write comprehensive tests for your API:</p>
<pre><code class="language-java">@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(locations = &quot;classpath:application-test.properties&quot;)
class UserControllerTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void shouldCreateUser() {
        User user = new User();
        user.setFirstName(&quot;John&quot;);
        user.setLastName(&quot;Doe&quot;);
        user.setEmail(&quot;john.doe@example.com&quot;);
        
        ResponseEntity&lt;User&gt; response = restTemplate.postForEntity(
            &quot;/api/users&quot;, user, User.class);
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().getEmail()).isEqualTo(&quot;john.doe@example.com&quot;);
    }
    
    @Test
    void shouldReturnUserById() {
        User savedUser = userRepository.save(createTestUser());
        
        ResponseEntity&lt;User&gt; response = restTemplate.getForEntity(
            &quot;/api/users/&quot; + savedUser.getId(), User.class);
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getId()).isEqualTo(savedUser.getId());
    }
}</code></pre>
<h2 id="best-practices">Best Practices</h2>
<h3 id="1-use-dtos-for-data-transfer">1. Use DTOs for Data Transfer</h3>
<p>Create separate DTOs for request and response:</p>
<pre><code class="language-java">public class UserCreateRequest {
    @NotBlank
    @Email
    private String email;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String firstName;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String lastName;
    
    // getters and setters
}</code></pre>
<h3 id="2-implement-pagination">2. Implement Pagination</h3>
<pre><code class="language-java">@GetMapping
public ResponseEntity&lt;Page&lt;User&gt;&gt; getAllUsers(
        @RequestParam(defaultValue = &quot;0&quot;) int page,
        @RequestParam(defaultValue = &quot;10&quot;) int size,
        @RequestParam(defaultValue = &quot;id&quot;) String sortBy) {
    
    Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
    Page&lt;User&gt; users = userService.getAllUsers(pageable);
    return ResponseEntity.ok(users);
}</code></pre>
<h3 id="3-add-api-documentation">3. Add API Documentation</h3>
<p>Use Swagger/OpenAPI:</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/api/users&quot;)
@Tag(name = &quot;User Management&quot;, description = &quot;APIs for managing users&quot;)
public class UserController {
    
    @Operation(summary = &quot;Get all users&quot;, description = &quot;Retrieve a paginated list of all users&quot;)
    @ApiResponses(value = {
        @ApiResponse(responseCode = &quot;200&quot;, description = &quot;Successfully retrieved users&quot;),
        @ApiResponse(responseCode = &quot;500&quot;, description = &quot;Internal server error&quot;)
    })
    @GetMapping
    public ResponseEntity&lt;Page&lt;User&gt;&gt; getAllUsers(/* parameters */) {
        // implementation
    }
}</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Building scalable REST APIs with Spring Boot involves following best practices for project structure, error handling, validation, and testing. By leveraging Spring Boot's powerful features and following these guidelines, you can create robust, maintainable APIs that scale with your application's needs.</p>
<p>Key takeaways:</p>
<ul><li>Use proper layered architecture (Controller → Service → Repository)</li><li>Implement comprehensive error handling</li><li>Add validation at multiple levels</li><li>Write thorough tests</li><li>Use DTOs for clean data transfer</li><li>Document your APIs properly</li><li>Consider pagination for large datasets</li></ul>
<p>With these foundations in place, your Spring Boot REST APIs will be well-equipped to handle production workloads and future growth.</p>]]></content:encoded>
    </item>
    <item>
      <title>Implementing JWT Authentication in Spring Security</title>
      <link>https://dahalutsab.com.np/dispatches/jwt-authentication-spring-security</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/jwt-authentication-spring-security</guid>
      <pubDate>Wed, 10 Jan 2024 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>Spring Boot</category>
      <category>JWT</category>
      <category>Spring Security</category>
      <category>Authentication</category>
      <category>Security</category>
      <description>Step-by-step implementation of JWT-based authentication and authorization in Spring Boot applications, including refresh token handling and security best practices.</description>
      <content:encoded><![CDATA[<p>JSON Web Tokens (JWT) have become the standard for stateless authentication in modern web applications. This guide will walk you through implementing JWT authentication in a Spring Boot application.</p>
<h2 id="introduction-to-jwt">Introduction to JWT</h2>
<p>JWT is a compact, URL-safe means of representing claims to be transferred between two parties. It consists of three parts separated by dots:</p>
<ul><li><strong>Header</strong>: Contains the type of token and signing algorithm</li><li><strong>Payload</strong>: Contains the claims (user data)</li><li><strong>Signature</strong>: Used to verify the token hasn't been tampered with</li></ul>
<h3 id="why-use-jwt">Why Use JWT?</h3>
<ul><li><strong>Stateless</strong>: No need to store sessions on the server</li><li><strong>Scalable</strong>: Perfect for microservices architecture</li><li><strong>Cross-domain</strong>: Can be used across different domains</li><li><strong>Mobile-friendly</strong>: Ideal for mobile applications</li></ul>
<h2 id="spring-security-setup">Spring Security Setup</h2>
<p>First, add the necessary dependencies to your project:</p>
<pre><code class="language-xml">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
        &lt;artifactId&gt;jjwt-api&lt;/artifactId&gt;
        &lt;version&gt;0.11.5&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
        &lt;artifactId&gt;jjwt-impl&lt;/artifactId&gt;
        &lt;version&gt;0.11.5&lt;/version&gt;
        &lt;scope&gt;runtime&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
        &lt;artifactId&gt;jjwt-jackson&lt;/artifactId&gt;
        &lt;version&gt;0.11.5&lt;/version&gt;
        &lt;scope&gt;runtime&lt;/scope&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<h3 id="application-properties">Application Properties</h3>
<p>Configure JWT settings in your application.properties:</p>
<pre><code class="language-properties">jwt.secret=mySecretKey
jwt.expiration=86400000
jwt.refresh.expiration=604800000</code></pre>
<h2 id="jwt-utility-class">JWT Utility Class</h2>
<p>Create a utility class to handle JWT operations:</p>
<pre><code class="language-java">@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value(&quot;${jwt.secret}&quot;)
    private String jwtSecret;

    @Value(&quot;${jwt.expiration}&quot;)
    private int jwtExpirationMs;

    @Value(&quot;${jwt.refresh.expiration}&quot;)
    private int refreshTokenExpirationMs;

    public String generateJwtToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        return generateTokenFromUsername(userPrincipal.getUsername());
    }

    public String generateTokenFromUsername(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String generateRefreshToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + refreshTokenExpirationMs))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error(&quot;Invalid JWT signature: {}&quot;, e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error(&quot;Invalid JWT token: {}&quot;, e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error(&quot;JWT token is expired: {}&quot;, e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error(&quot;JWT token is unsupported: {}&quot;, e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error(&quot;JWT claims string is empty: {}&quot;, e.getMessage());
        }
        return false;
    }
}</code></pre>
<h2 id="authentication-filter">Authentication Filter</h2>
<p>Create a filter to process JWT tokens:</p>
<pre><code class="language-java">public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsService userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = parseJwt(request);
            if (jwt != null &amp;&amp; jwtUtils.validateJwtToken(jwt)) {
                String username = jwtUtils.getUserNameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null,
                                userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error(&quot;Cannot set user authentication: {}&quot;, e);
        }

        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader(&quot;Authorization&quot;);

        if (StringUtils.hasText(headerAuth) &amp;&amp; headerAuth.startsWith(&quot;Bearer &quot;)) {
            return headerAuth.substring(7);
        }

        return null;
    }
}</code></pre>
<h3 id="security-configuration">Security Configuration</h3>
<p>Configure Spring Security:</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig)
            throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeHttpRequests()
                .requestMatchers(&quot;/api/auth/**&quot;).permitAll()
                .requestMatchers(&quot;/api/public/**&quot;).permitAll()
                .anyRequest().authenticated();

        http.authenticationProvider(authenticationProvider());
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}</code></pre>
<h2 id="auth-controllers">Auth Controllers</h2>
<p>Create authentication endpoints:</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/api/auth&quot;)
public class AuthController {
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtUtils jwtUtils;

    @PostMapping(&quot;/signin&quot;)
    public ResponseEntity&lt;?&gt; authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
                        loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);
        String refreshToken = jwtUtils.generateRefreshToken(loginRequest.getUsername());

        UserPrincipal userDetails = (UserPrincipal) authentication.getPrincipal();
        List&lt;String&gt; roles = userDetails.getAuthorities().stream()
                .map(item -&gt; item.getAuthority())
                .collect(Collectors.toList());

        return ResponseEntity.ok(new JwtResponse(jwt, refreshToken,
                userDetails.getId(),
                userDetails.getUsername(),
                userDetails.getEmail(),
                roles));
    }

    @PostMapping(&quot;/signup&quot;)
    public ResponseEntity&lt;?&gt; registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
            return ResponseEntity.badRequest()
                    .body(new MessageResponse(&quot;Error: Username is already taken!&quot;));
        }

        if (userRepository.existsByEmail(signUpRequest.getEmail())) {
            return ResponseEntity.badRequest()
                    .body(new MessageResponse(&quot;Error: Email is already in use!&quot;));
        }

        User user = new User(signUpRequest.getUsername(),
                signUpRequest.getEmail(),
                encoder.encode(signUpRequest.getPassword()));

        Set&lt;String&gt; strRoles = signUpRequest.getRole();
        Set&lt;Role&gt; roles = new HashSet&lt;&gt;();

        if (strRoles == null) {
            Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                    .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
            roles.add(userRole);
        } else {
            strRoles.forEach(role -&gt; {
                switch (role) {
                    case &quot;admin&quot;:
                        Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
                                .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
                        roles.add(adminRole);
                        break;
                    default:
                        Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                                .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
                        roles.add(userRole);
                }
            });
        }

        user.setRoles(roles);
        userRepository.save(user);

        return ResponseEntity.ok(new MessageResponse(&quot;User registered successfully!&quot;));
    }

    @PostMapping(&quot;/refresh&quot;)
    public ResponseEntity&lt;?&gt; refreshToken(@Valid @RequestBody TokenRefreshRequest request) {
        String requestRefreshToken = request.getRefreshToken();

        if (jwtUtils.validateJwtToken(requestRefreshToken)) {
            String username = jwtUtils.getUserNameFromJwtToken(requestRefreshToken);
            String token = jwtUtils.generateTokenFromUsername(username);
            return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
        } else {
            return ResponseEntity.badRequest()
                    .body(new MessageResponse(&quot;Refresh token is not valid!&quot;));
        }
    }
}</code></pre>
<h2 id="testing-security">Testing Security</h2>
<p>Create tests to verify your JWT implementation:</p>
<pre><code class="language-java">@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class AuthControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldAuthenticateUser() {
        LoginRequest loginRequest = new LoginRequest();
        loginRequest.setUsername(&quot;testuser&quot;);
        loginRequest.setPassword(&quot;password&quot;);

        ResponseEntity&lt;JwtResponse&gt; response = restTemplate.postForEntity(
                &quot;/api/auth/signin&quot;, loginRequest, JwtResponse.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getAccessToken()).isNotNull();
        assertThat(response.getBody().getRefreshToken()).isNotNull();
    }

    @Test
    void shouldRegisterNewUser() {
        SignupRequest signupRequest = new SignupRequest();
        signupRequest.setUsername(&quot;newuser&quot;);
        signupRequest.setEmail(&quot;newuser@example.com&quot;);
        signupRequest.setPassword(&quot;password123&quot;);

        ResponseEntity&lt;MessageResponse&gt; response = restTemplate.postForEntity(
                &quot;/api/auth/signup&quot;, signupRequest, MessageResponse.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getMessage()).contains(&quot;User registered successfully&quot;);
    }

    @Test
    void shouldRefreshToken() {
        // First, authenticate to get tokens
        String refreshToken = authenticateAndGetRefreshToken();

        TokenRefreshRequest refreshRequest = new TokenRefreshRequest();
        refreshRequest.setRefreshToken(refreshToken);

        ResponseEntity&lt;TokenRefreshResponse&gt; response = restTemplate.postForEntity(
                &quot;/api/auth/refresh&quot;, refreshRequest, TokenRefreshResponse.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getAccessToken()).isNotNull();
    }
}</code></pre>
<h2 id="best-practices">Best Practices</h2>
<h3 id="1-secure-token-storage">1. Secure Token Storage</h3>
<ul><li>Store tokens securely on the client side</li><li>Use HttpOnly cookies for web applications</li><li>Implement proper token rotation</li></ul>
<h3 id="2-token-validation">2. Token Validation</h3>
<ul><li>Always validate tokens on the server side</li><li>Check token expiration</li><li>Verify token signature</li></ul>
<h3 id="3-error-handling">3. Error Handling</h3>
<pre><code class="language-java">@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        logger.error(&quot;Unauthorized error: {}&quot;, authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, &quot;Error: Unauthorized&quot;);
    }
}</code></pre>
<h3 id="4-rate-limiting">4. Rate Limiting</h3>
<p>Implement rate limiting to prevent brute force attacks:</p>
<pre><code class="language-java">@Component
public class LoginAttemptService {
    private final int MAX_ATTEMPT = 3;
    private LoadingCache&lt;String, Integer&gt; attemptsCache;

    public LoginAttemptService() {
        super();
        attemptsCache = CacheBuilder.newBuilder()
                .expireAfterWrite(1, TimeUnit.DAYS)
                .build(new CacheLoader&lt;String, Integer&gt;() {
                    public Integer load(String key) {
                        return 0;
                    }
                });
    }

    public void loginSucceeded(String key) {
        attemptsCache.invalidate(key);
    }

    public void loginFailed(String key) {
        int attempts = 0;
        try {
            attempts = attemptsCache.get(key);
        } catch (ExecutionException e) {
            attempts = 0;
        }
        attempts++;
        attemptsCache.put(key, attempts);
    }

    public boolean isBlocked(String key) {
        try {
            return attemptsCache.get(key) &gt;= MAX_ATTEMPT;
        } catch (ExecutionException e) {
            return false;
        }
    }
}</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Implementing JWT authentication in Spring Security provides a robust, scalable solution for securing your applications. Key benefits include:</p>
<ul><li><strong>Stateless authentication</strong>: No server-side session storage required</li><li><strong>Scalability</strong>: Perfect for distributed systems</li><li><strong>Flexibility</strong>: Works across different platforms and domains</li><li><strong>Security</strong>: Strong cryptographic signatures ensure token integrity</li></ul>
<p>Remember to follow security best practices:</p>
<ul><li>Use strong secret keys</li><li>Implement proper token expiration</li><li>Add rate limiting</li><li>Validate all tokens server-side</li><li>Use HTTPS in production</li></ul>
<p>With this implementation, you have a solid foundation for JWT-based authentication that can scale with your application's needs.</p>]]></content:encoded>
    </item>
    <item>
      <title>Database Optimization Techniques for Spring Data JPA</title>
      <link>https://dahalutsab.com.np/dispatches/database-optimization-spring-data-jpa</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/database-optimization-spring-data-jpa</guid>
      <pubDate>Fri, 05 Jan 2024 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>Database</category>
      <category>JPA</category>
      <category>Database</category>
      <category>Performance</category>
      <category>Optimization</category>
      <description>Advanced techniques for optimizing database queries in Spring Data JPA applications, including lazy loading, query optimization, and performance monitoring.</description>
      <content:encoded><![CDATA[<p>Database performance is crucial for any application's success. This guide covers advanced optimization techniques for Spring Data JPA applications.</p>
<h2 id="introduction">Introduction</h2>
<p>Spring Data JPA simplifies database operations, but without proper optimization, it can lead to performance issues. Understanding how JPA works under the hood is essential for building efficient applications.</p>
<h3 id="common-performance-issues">Common Performance Issues</h3>
<ul><li>N+1 query problems</li><li>Inefficient lazy loading</li><li>Missing indexes</li><li>Poor query design</li><li>Lack of caching</li></ul>
<h2 id="n-1-query-problem">N+1 Query Problem</h2>
<p>The N+1 problem occurs when you fetch a list of entities and then access their related entities, causing additional queries.</p>
<h3 id="problem-example">Problem Example</h3>
<pre><code class="language-java">@Entity
public class Author {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = &quot;author&quot;, fetch = FetchType.LAZY)
    private List&lt;Book&gt; books;
}

@Entity
public class Book {
    @Id
    private Long id;
    private String title;
    
    @ManyToOne
    private Author author;
}

// This causes N+1 queries
List&lt;Author&gt; authors = authorRepository.findAll();
for (Author author : authors) {
    System.out.println(author.getBooks().size()); // Triggers additional query
}</code></pre>
<h3 id="solutions">Solutions</h3>
<h4 id="1-use-entitygraph">1. Use @EntityGraph</h4>
<pre><code class="language-java">@Repository
public interface AuthorRepository extends JpaRepository&lt;Author, Long&gt; {
    
    @EntityGraph(attributePaths = {&quot;books&quot;})
    List&lt;Author&gt; findAllWithBooks();
    
    @EntityGraph(attributePaths = {&quot;books&quot;, &quot;books.publisher&quot;})
    List&lt;Author&gt; findAllWithBooksAndPublisher();
}</code></pre>
<h4 id="2-use-join-fetch-in-jpql">2. Use JOIN FETCH in JPQL</h4>
<pre><code class="language-java">@Query(&quot;SELECT a FROM Author a JOIN FETCH a.books&quot;)
List&lt;Author&gt; findAllAuthorsWithBooks();

@Query(&quot;SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books b LEFT JOIN FETCH b.publisher&quot;)
List&lt;Author&gt; findAllAuthorsWithBooksAndPublisher();</code></pre>
<h4 id="3-use-projections">3. Use Projections</h4>
<pre><code class="language-java">public interface AuthorBookProjection {
    Long getId();
    String getName();
    List&lt;BookProjection&gt; getBooks();
    
    interface BookProjection {
        Long getId();
        String getTitle();
    }
}

@Query(&quot;SELECT a FROM Author a JOIN FETCH a.books&quot;)
List&lt;AuthorBookProjection&gt; findAllAuthorsWithBooksProjection();</code></pre>
<h2 id="lazy-loading-strategies">Lazy Loading Strategies</h2>
<h3 id="understanding-fetch-types">Understanding Fetch Types</h3>
<pre><code class="language-java">@Entity
public class Order {
    @Id
    private Long id;
    
    // Eager loading - always fetched
    @OneToMany(fetch = FetchType.EAGER)
    private List&lt;OrderItem&gt; items;
    
    // Lazy loading - fetched when accessed
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;
}</code></pre>
<h3 id="batch-fetching">Batch Fetching</h3>
<pre><code class="language-java">@Entity
@BatchSize(size = 10)
public class Author {
    @Id
    private Long id;
    
    @OneToMany(mappedBy = &quot;author&quot;)
    @BatchSize(size = 5)
    private List&lt;Book&gt; books;
}</code></pre>
<h3 id="subselect-fetching">Subselect Fetching</h3>
<pre><code class="language-java">@Entity
public class Department {
    @OneToMany(mappedBy = &quot;department&quot;)
    @Fetch(FetchMode.SUBSELECT)
    private List&lt;Employee&gt; employees;
}</code></pre>
<h2 id="query-optimization">Query Optimization</h2>
<h3 id="custom-queries-with-pagination">Custom Queries with Pagination</h3>
<pre><code class="language-java">@Repository
public interface BookRepository extends JpaRepository&lt;Book, Long&gt; {
    
    @Query(value = &quot;SELECT b FROM Book b WHERE b.publishedDate &gt;= :date&quot;,
           countQuery = &quot;SELECT count(b) FROM Book b WHERE b.publishedDate &gt;= :date&quot;)
    Page&lt;Book&gt; findRecentBooks(@Param(&quot;date&quot;) LocalDate date, Pageable pageable);
    
    @Query(&quot;SELECT new com.example.dto.BookSummary(b.id, b.title, a.name) &quot; +
           &quot;FROM Book b JOIN b.author a WHERE b.genre = :genre&quot;)
    List&lt;BookSummary&gt; findBookSummariesByGenre(@Param(&quot;genre&quot;) String genre);
}</code></pre>
<h3 id="native-queries-for-complex-operations">Native Queries for Complex Operations</h3>
<pre><code class="language-java">@Query(value = &quot;SELECT * FROM books b &quot; +
               &quot;WHERE b.rating &gt; :rating &quot; +
               &quot;AND b.published_date BETWEEN :startDate AND :endDate &quot; +
               &quot;ORDER BY b.rating DESC, b.published_date DESC&quot;,
       nativeQuery = true)
List&lt;Book&gt; findTopRatedBooksInPeriod(@Param(&quot;rating&quot;) Double rating,
                                     @Param(&quot;startDate&quot;) LocalDate startDate,
                                     @Param(&quot;endDate&quot;) LocalDate endDate);</code></pre>
<h3 id="bulk-operations">Bulk Operations</h3>
<pre><code class="language-java">@Modifying
@Query(&quot;UPDATE Book b SET b.price = b.price * 1.1 WHERE b.genre = :genre&quot;)
int increasePriceByGenre(@Param(&quot;genre&quot;) String genre);

@Modifying
@Query(&quot;DELETE FROM Book b WHERE b.publishedDate &lt; :date&quot;)
int deleteOldBooks(@Param(&quot;date&quot;) LocalDate date);</code></pre>
<h2 id="caching-strategies">Caching Strategies</h2>
<h3 id="second-level-cache">Second-Level Cache</h3>
<pre><code class="language-java">@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Category {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = &quot;category&quot;)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private List&lt;Product&gt; products;
}</code></pre>
<h3 id="query-result-cache">Query Result Cache</h3>
<pre><code class="language-java">@Repository
public interface ProductRepository extends JpaRepository&lt;Product, Long&gt; {
    
    @QueryHints(@QueryHint(name = &quot;org.hibernate.cacheable&quot;, value = &quot;true&quot;))
    @Query(&quot;SELECT p FROM Product p WHERE p.featured = true&quot;)
    List&lt;Product&gt; findFeaturedProducts();
}</code></pre>
<h3 id="spring-cache-abstraction">Spring Cache Abstraction</h3>
<pre><code class="language-java">@Service
@Transactional
public class ProductService {
    
    @Cacheable(value = &quot;products&quot;, key = &quot;#id&quot;)
    public Product findById(Long id) {
        return productRepository.findById(id).orElse(null);
    }
    
    @CacheEvict(value = &quot;products&quot;, key = &quot;#product.id&quot;)
    public Product save(Product product) {
        return productRepository.save(product);
    }
    
    @CacheEvict(value = &quot;products&quot;, allEntries = true)
    public void clearCache() {
        // Method implementation
    }
}</code></pre>
<h2 id="performance-monitoring">Performance Monitoring</h2>
<h3 id="enable-sql-logging">Enable SQL Logging</h3>
<pre><code class="language-properties"># Show SQL queries
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Show parameter values
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# Show statistics
spring.jpa.properties.hibernate.generate_statistics=true</code></pre>
<h3 id="custom-performance-interceptor">Custom Performance Interceptor</h3>
<pre><code class="language-java">@Component
public class QueryCountInterceptor implements Interceptor {
    private static final ThreadLocal&lt;Integer&gt; queryCount = new ThreadLocal&lt;&gt;();
    
    public static void startCounter() {
        queryCount.set(0);
    }
    
    public static int getQueryCount() {
        return queryCount.get() == null ? 0 : queryCount.get();
    }
    
    public static void clear() {
        queryCount.remove();
    }
    
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        incrementCounter();
        return false;
    }
    
    private void incrementCounter() {
        Integer count = queryCount.get();
        if (count == null) {
            count = 0;
        }
        queryCount.set(count + 1);
    }
}</code></pre>
<h3 id="performance-testing">Performance Testing</h3>
<pre><code class="language-java">@Test
public void testQueryPerformance() {
    QueryCountInterceptor.startCounter();
    
    List&lt;Author&gt; authors = authorRepository.findAllWithBooks();
    
    int queryCount = QueryCountInterceptor.getQueryCount();
    assertThat(queryCount).isLessThanOrEqualTo(1); // Should be 1 query with JOIN FETCH
    
    QueryCountInterceptor.clear();
}</code></pre>
<h2 id="database-indexing">Database Indexing</h2>
<h3 id="jpa-index-annotations">JPA Index Annotations</h3>
<pre><code class="language-java">@Entity
@Table(indexes = {
    @Index(name = &quot;idx_book_title&quot;, columnList = &quot;title&quot;),
    @Index(name = &quot;idx_book_author_genre&quot;, columnList = &quot;author_id, genre&quot;),
    @Index(name = &quot;idx_book_published_date&quot;, columnList = &quot;published_date&quot;)
})
public class Book {
    @Id
    private Long id;
    
    @Column(length = 255)
    private String title;
    
    @Column(length = 100)
    private String genre;
    
    @Column(name = &quot;published_date&quot;)
    private LocalDate publishedDate;
    
    @ManyToOne
    @JoinColumn(name = &quot;author_id&quot;)
    private Author author;
}</code></pre>
<h3 id="composite-indexes">Composite Indexes</h3>
<pre><code class="language-java">@Entity
@Table(indexes = {
    @Index(name = &quot;idx_order_customer_date&quot;, 
           columnList = &quot;customer_id, order_date DESC&quot;),
    @Index(name = &quot;idx_order_status_date&quot;, 
           columnList = &quot;status, order_date&quot;)
})
public class Order {
    // Entity fields
}</code></pre>
<h2 id="connection-pool-optimization">Connection Pool Optimization</h2>
<h3 id="hikaricp-configuration">HikariCP Configuration</h3>
<pre><code class="language-properties"># Connection pool settings
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=600000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.leak-detection-threshold=60000</code></pre>
<h2 id="best-practices-summary">Best Practices Summary</h2>
<h3 id="1-entity-design">1. Entity Design</h3>
<ul><li>Use appropriate fetch types</li><li>Implement equals() and hashCode() properly</li><li>Use @BatchSize for collections</li><li>Consider using DTOs for read operations</li></ul>
<h3 id="2-query-optimization">2. Query Optimization</h3>
<ul><li>Use projections for read-only operations</li><li>Implement pagination for large datasets</li><li>Use native queries for complex operations</li><li>Avoid N+1 problems with JOIN FETCH</li></ul>
<h3 id="3-caching-strategy">3. Caching Strategy</h3>
<ul><li>Enable second-level cache for reference data</li><li>Use query result cache for expensive queries</li><li>Implement application-level caching with Spring Cache</li></ul>
<h3 id="4-monitoring-and-testing">4. Monitoring and Testing</h3>
<ul><li>Enable SQL logging in development</li><li>Monitor query counts and execution times</li><li>Write performance tests</li><li>Use database profiling tools</li></ul>
<h2 id="conclusion">Conclusion</h2>
<p>Database optimization in Spring Data JPA requires understanding of JPA internals, proper entity design, and strategic use of caching. By following these techniques, you can significantly improve your application's performance:</p>
<ul><li>Eliminate N+1 query problems</li><li>Use appropriate loading strategies</li><li>Implement effective caching</li><li>Monitor and measure performance</li><li>Design proper database indexes</li></ul>
<p>Remember that optimization is an iterative process. Always measure before and after implementing changes to ensure they provide the expected performance improvements.</p>]]></content:encoded>
    </item>
    <item>
      <title>Introduction to Microservices Architecture</title>
      <link>https://dahalutsab.com.np/dispatches/introduction-microservices-architecture</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/introduction-microservices-architecture</guid>
      <pubDate>Mon, 01 Jan 2024 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>Microservices</category>
      <category>Microservices</category>
      <category>Spring Boot</category>
      <category>Docker</category>
      <category>Kubernetes</category>
      <category>Architecture</category>
      <description>Learn the fundamentals of microservices architecture and how to implement scalable, resilient systems using Spring Boot, Docker, and Kubernetes.</description>
      <content:encoded><![CDATA[<p>Microservices architecture is a powerful approach for building scalable, resilient, and maintainable applications. This comprehensive guide explores the fundamentals of microservices, their benefits and challenges, key design patterns, and practical implementation using Spring Boot, Docker, and Kubernetes. Whether you're transitioning from a monolithic architecture or building a new system, this tutorial provides actionable insights and code examples to help you succeed.</p>
<h2 id="what-are-microservices">What are Microservices?</h2>
<p>Microservices architecture structures an application as a collection of small, independent services, each focusing on a specific business capability. These services communicate through well-defined APIs or message queues and can be developed, deployed, and scaled independently.</p>
<h3 id="key-characteristics">Key Characteristics</h3>
<ul><li><strong>Single Responsibility</strong>: Each microservice handles one specific function, such as user management or order processing.</li><li><strong>Decentralized Data Management</strong>: Each service manages its own database, reducing dependencies.</li><li><strong>Technology Agnostic</strong>: Services can use different programming languages, frameworks, or databases.</li><li><strong>Independent Deployment</strong>: Services can be updated or deployed without affecting the entire system.</li><li><strong>Fault Isolation</strong>: A failure in one service does not necessarily impact others.</li></ul>
<h3 id="why-microservices">Why Microservices?</h3>
<p>Microservices are ideal for large-scale applications requiring flexibility, scalability, and team autonomy. They align well with modern DevOps practices and cloud-native environments, enabling faster development cycles and easier maintenance.</p>
<h2 id="benefits-and-challenges">Benefits and Challenges</h2>
<h3 id="benefits">Benefits</h3>
<ol><li><strong>Scalability</strong>: Scale individual services based on demand, optimizing resource usage.</li><li><strong>Flexibility</strong>: Use the best technology stack for each service (e.g., Java for backend services, Python for data processing).</li><li><strong>Team Autonomy</strong>: Independent teams can work on different services, accelerating development.</li><li><strong>Resilience</strong>: Fault isolation ensures that a failure in one service doesn’t bring down the entire system.</li><li><strong>Faster Releases</strong>: Smaller codebases enable quicker updates and deployments.</li></ol>
<h3 id="challenges">Challenges</h3>
<ol><li><strong>Distributed System Complexity</strong>: Managing inter-service communication and data consistency is complex.</li><li><strong>Network Latency</strong>: Service-to-service calls over the network introduce latency.</li><li><strong>Data Management</strong>: Ensuring consistency across distributed databases requires careful design (e.g., eventual consistency).</li><li><strong>Testing Complexity</strong>: Integration and end-to-end testing are more challenging than in monolithic applications.</li><li><strong>Monitoring Overhead</strong>: Comprehensive observability is needed to track distributed services.</li></ol>
<h3 id="real-world-use-case">Real-World Use Case</h3>
<p>Imagine an e-commerce platform with features like user management, product catalog, order processing, and payment handling. In a monolithic architecture, all features share a single codebase and database, making scaling and updates difficult. With microservices, each feature becomes a separate service, allowing independent scaling (e.g., scaling the order service during a flash sale) and enabling parallel development by multiple teams.</p>
<h2 id="design-patterns">Design Patterns</h2>
<p>Microservices architecture leverages several design patterns to address common challenges. Below are key patterns with practical implementations using Spring Boot.</p>
<h3 id="service-discovery-with-eureka">Service Discovery with Eureka</h3>
<p>Service discovery allows microservices to locate and communicate with each other dynamically. Spring Cloud Netflix Eureka is a popular service registry.</p>
<pre><code class="language-java">import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}</code></pre>
<p><strong>Client Configuration</strong> (in a microservice):</p>
<pre><code class="language-java">import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}</code></pre>
<p><strong>application.yml</strong> (for User Service):</p>
<pre><code class="language-yaml">eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: user-service
server:
  port: 8081</code></pre>
<h3 id="api-gateway-with-spring-cloud-gateway">API Gateway with Spring Cloud Gateway</h3>
<p>An API Gateway provides a single entry point for client requests, routing them to the appropriate microservice.</p>
<pre><code class="language-java">import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(&quot;user-service&quot;, r -&gt; r.path(&quot;/api/users/**&quot;)
                        .uri(&quot;lb://user-service&quot;))
                .route(&quot;order-service&quot;, r -&gt; r.path(&quot;/api/orders/**&quot;)
                        .uri(&quot;lb://order-service&quot;))
                .build();
    }
}</code></pre>
<p><strong>application.yml</strong> (for API Gateway):</p>
<pre><code class="language-yaml">spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8080</code></pre>
<h3 id="circuit-breaker-with-resilience4j">Circuit Breaker with Resilience4j</h3>
<p>Circuit breakers prevent cascading failures by providing fallback mechanisms when a service is unavailable.</p>
<pre><code class="language-java">import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class OrderServiceClient {
    private final RestTemplate restTemplate;

    public OrderServiceClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = &quot;orderService&quot;, fallbackMethod = &quot;getDefaultOrder&quot;)
    public Order getOrder(Long orderId) {
        return restTemplate.getForObject(&quot;http://order-service/api/orders/&quot; + orderId, Order.class);
    }

    public Order getDefaultOrder(Long orderId, Throwable throwable) {
        return new Order(orderId, &quot;Default Order&quot;, 0.0, &quot;UNAVAILABLE&quot;);
    }
}</code></pre>
<p><strong>application.yml</strong> (for circuit breaker):</p>
<pre><code class="language-yaml">resilience4j.circuitbreaker:
  instances:
    orderService:
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 10000
      permittedNumberOfCallsInHalfOpenState: 5</code></pre>
<h3 id="event-driven-communication-with-kafka">Event-Driven Communication with Kafka</h3>
<p>Asynchronous communication reduces coupling between services. Apache Kafka is used for event-driven interactions.</p>
<pre><code class="language-java">import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderEventProducer {
    private final KafkaTemplate&lt;String, Object&gt; kafkaTemplate;

    public OrderEventProducer(KafkaTemplate&lt;String, Object&gt; kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publishOrderCreated(Order order) {
        OrderCreatedEvent event = new OrderCreatedEvent(
                order.getId(),
                order.getCustomerId(),
                order.getTotal(),
                Instant.now()
        );
        kafkaTemplate.send(&quot;order-events&quot;, order.getId().toString(), event);
    }
}</code></pre>
<pre><code class="language-java">import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventConsumer {
    private static final Logger logger = LoggerFactory.getLogger(OrderEventConsumer.class);

    @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;notification-service&quot;)
    public void handleOrderCreated(OrderCreatedEvent event) {
        logger.info(&quot;Received order created event for order ID: {}&quot;, event.getOrderId());
        // Send notification to customer
        notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
    }
}</code></pre>
<h2 id="implementation-with-spring-boot">Implementation with Spring Boot</h2>
<p>Below is an implementation of a simple e-commerce system with two microservices: User Service and Order Service.</p>
<h3 id="user-service">User Service</h3>
<p><strong>Entity</strong>:</p>
<pre><code class="language-java">import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = &quot;users&quot;)
public class User {
    @Id
    private Long id;

    private String username;
    private String email;

    // Constructors, getters, and setters
}</code></pre>
<p><strong>Repository</strong>:</p>
<pre><code class="language-java">import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByEmail(String email);
}</code></pre>
<p><strong>Controller</strong>:</p>
<pre><code class="language-java">import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity&lt;User&gt; createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;User&gt; getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(user);
    }
}</code></pre>
<p><strong>Service</strong>:</p>
<pre><code class="language-java">import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new RuntimeException(&quot;Email already exists&quot;);
        }
        return userRepository.save(user);
    }

    public User getUser(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -&gt; new RuntimeException(&quot;User not found&quot;));
    }
}</code></pre>
<h3 id="order-service">Order Service</h3>
<p><strong>Entity</strong>:</p>
<pre><code class="language-java">import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = &quot;orders&quot;)
public class Order {
    @Id
    private Long id;

    private Long customerId;
    private Double total;
    private String status;

    // Constructors, getters, and setters
}</code></pre>
<p><strong>Repository</strong>:</p>
<pre><code class="language-java">import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository&lt;Order, Long&gt; {
    List&lt;Order&gt; findByCustomerId(Long customerId);
}</code></pre>
<p><strong>Controller</strong>:</p>
<pre><code class="language-java">import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/orders&quot;)
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity&lt;Order&gt; createOrder(@RequestBody Order order) {
        Order createdOrder = orderService.createOrder(order);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdOrder);
    }

    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Order&gt; getOrder(@PathVariable Long id) {
        Order order = orderService.getOrder(id);
        return ResponseEntity.ok(order);
    }
}</code></pre>
<p><strong>Service</strong>:</p>
<pre><code class="language-java">import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final OrderEventProducer eventProducer;

    public OrderService(OrderRepository orderRepository, OrderEventProducer eventProducer) {
        this.orderRepository = orderRepository;
        this.eventProducer = eventProducer;
    }

    public Order createOrder(Order order) {
        Order savedOrder = orderRepository.save(order);
        eventProducer.publishOrderCreated(savedOrder);
        return savedOrder;
    }

    public Order getOrder(Long id) {
        return orderRepository.findById(id)
                .orElseThrow(() -&gt; new RuntimeException(&quot;Order not found&quot;));
    }
}</code></pre>
<h3 id="configuration">Configuration</h3>
<p><strong>application.yml</strong> (for User Service):</p>
<pre><code class="language-yaml">spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://localhost:3306/user_db
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8081</code></pre>
<p><strong>application.yml</strong> (for Order Service):</p>
<pre><code class="language-yaml">spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://localhost:3306/order_db
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8082</code></pre>
<h2 id="deployment-strategies">Deployment Strategies</h2>
<p>Deploying microservices requires orchestration and containerization for scalability and reliability.</p>
<h3 id="dockerizing-microservices">Dockerizing Microservices</h3>
<p><strong>Dockerfile</strong> (for User Service):</p>
<pre><code class="language-dockerfile"># Build stage
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Runtime stage
FROM openjdk:17-jdk-slim
RUN addgroup --system spring &amp;&amp; adduser --system spring --ingroup spring
WORKDIR /app
COPY --from=build /app/target/user-service-1.0.0.jar app.jar
RUN chown spring:spring app.jar
USER spring:spring
EXPOSE 8081
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre>
<p><strong>Docker Compose</strong> (for orchestrating services):</p>
<pre><code class="language-yaml">version: '3.8'
services:
  eureka-server:
    build: ./eureka-server
    ports:
      - &quot;8761:8761&quot;
  user-service:
    build: ./user-service
    ports:
      - &quot;8081:8081&quot;
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    depends_on:
      - eureka-server
      - user-db
  order-service:
    build: ./order-service
    ports:
      - &quot;8082:8082&quot;
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    depends_on:
      - eureka-server
      - order-db
      - kafka
  user-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=user_db
    ports:
      - &quot;3307:3306&quot;
  order-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=order_db
    ports:
      - &quot;3308:3306&quot;
  kafka:
    image: confluentinc/cp-kafka:7.3.0
    environment:
      - KAFKA_BROKER_ID=1
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
    ports:
      - &quot;9092:9092&quot;
    depends_on:
      - zookeeper
  zookeeper:
    image: confluentinc/cp-zookeeper:7.3.0
    environment:
      - ZOOKEEPER_CLIENT_PORT=2181
      - ZOOKEEPER_TICK_TIME=2000
    ports:
      - &quot;2181:2181&quot;</code></pre>
<h3 id="kubernetes-deployment">Kubernetes Deployment</h3>
<p><strong>Deployment YAML</strong> (for User Service):</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: myregistry/user-service:1.0.0
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: &quot;prod&quot;
        - name: EUREKA_CLIENT_SERVICEURL_DEFAULTZONE
          value: &quot;http://eureka-server:8761/eureka/&quot;</code></pre>
<p><strong>Service YAML</strong> (for User Service):</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8081
  type: ClusterIP</code></pre>
<h2 id="monitoring-and-logging">Monitoring and Logging</h2>
<p>Effective monitoring and logging are essential for maintaining microservices.</p>
<p><strong>Prometheus Configuration</strong> (in <code>application.yml</code>):</p>
<pre><code class="language-yaml">management:
  endpoints:
    web:
      exposure:
        include: prometheus, health, info</code></pre>
<p><strong>Prometheus Scrape Config</strong> (<code>prometheus.yml</code>):</p>
<pre><code class="language-yaml">scrape_configs:
  - job_name: 'user-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['user-service:8081']
  - job_name: 'order-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['order-service:8082']</code></pre>
<p><strong>Centralized Logging</strong>:</p>
<pre><code class="language-java">import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class LogService {
    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    public void logAction(String action) {
        logger.info(&quot;Action performed: {}&quot;, action);
    }
}</code></pre>
<h2 id="best-practices">Best Practices</h2>
<ol><li><strong>Domain-Driven Design</strong>:</li></ol>
<ul><li>Align services with business domains (e.g., user management, order processing).</li><li>Example: Separate services for inventory, payments, and shipping.</li></ul>
<ol><li><strong>Database per Service</strong>:</li></ol>
<ul><li>Use separate databases for each service to ensure loose coupling.</li><li>Example: Use Flyway for database migrations:</li></ul>
<pre><code class="language-java">     @Component
     public class DatabaseMigrationService {
         @Autowired
         private Flyway flyway;

         @PostConstruct
         public void migrate() {
             flyway.migrate();
         }
     }</code></pre>
<ol><li><strong>API Versioning</strong>:</li></ol>
<ul><li>Version APIs to handle breaking changes (e.g., <code>/ api / v1 / users</code>).</li><li>Example:</li></ul>
<pre><code class="language-java">     @RestController
     @RequestMapping(&quot;/api/v1/users&quot;)
     public class UserController {
         // Controller logic
     }</code></pre>
<ol><li><strong>Resilience</strong>:</li></ol>
<ul><li>Use retries and timeouts for inter-service calls.</li><li>Example with Resilience4j retry:</li></ul>
<pre><code class="language-java">     @Retry(name = &quot;orderService&quot;, fallbackMethod = &quot;retryFallback&quot;)
     public Order getOrder(Long orderId) {
         return restTemplate.getForObject(&quot;http://order-service/api/orders/&quot; + orderId, Order.class);
     }

     public Order retryFallback(Long orderId, Throwable throwable) {
         return new Order(orderId, &quot;Retry Failed&quot;, 0.0, &quot;ERROR&quot;);
     }</code></pre>
<ol><li><strong>Security</strong>:</li></ol>
<ul><li>Secure APIs with JWT or OAuth2.</li><li>Implement rate limiting to prevent abuse:</li></ul>
<pre><code class="language-java">     @Component
     public class RateLimiter {
         private final RateLimiterRegistry registry = RateLimiterRegistry.ofDefaults();

         @PostMapping(&quot;/api/users&quot;)
         @RateLimiter(name = &quot;userCreation&quot;, fallbackMethod = &quot;rateLimitFallback&quot;)
         public ResponseEntity&lt;User&gt; createUser(@Valid @RequestBody User user) {
             User createdUser = userService.createUser(user);
             return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
         }

         public ResponseEntity&lt;?&gt; rateLimitFallback(User user, RateLimitException ex) {
             return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                     .body(new ErrorResponse(HttpStatus.TOO_MANY_REQUESTS.value(), &quot;Rate limit exceeded&quot;));
         }
     }</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Microservices architecture enables scalable, flexible, and resilient applications. By using Spring Boot for development, Docker for containerization, and Kubernetes for orchestration, you can build robust systems. Key takeaways:</p>
<ul><li>Design services with single responsibilities and clear boundaries.</li><li>Use service discovery, API gateways, and circuit breakers for resilience.</li><li>Implement event-driven communication with Kafka for loose coupling.</li><li>Monitor services with Prometheus and centralized logging.</li><li>Follow best practices like domain-driven design and database-per-service patterns.</li></ul>
<p><strong>Call to Action</strong>: Start building your microservices-based application using the examples provided. Deploy locally with Docker Compose and scale to production with Kubernetes. Share your feedback or questions in the comments or on X to join the developer community!</p>]]></content:encoded>
    </item>
    <item>
      <title>Event-Driven Architecture with Apache Kafka</title>
      <link>https://dahalutsab.com.np/dispatches/event-driven-architecture-apache-kafka</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/event-driven-architecture-apache-kafka</guid>
      <pubDate>Thu, 28 Dec 2023 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>System Design</category>
      <category>Kafka</category>
      <category>Event-Driven</category>
      <category>Spring Boot</category>
      <category>Messaging</category>
      <category>Microservices</category>
      <description>Learn how to implement event-driven architecture using Apache Kafka and Spring Boot, with practical examples for building scalable, resilient systems.</description>
      <content:encoded><![CDATA[<p>Event-driven architecture (EDA) is a powerful paradigm for building scalable, loosely coupled, and resilient distributed systems. This comprehensive guide explores EDA concepts, Apache Kafka fundamentals, and practical implementation using Spring Boot. With detailed code examples and best practices, you'll learn how to design and deploy event-driven systems for real-world applications.</p>
<h2 id="event-driven-architecture">Event-Driven Architecture</h2>
<p>Event-driven architecture enables services to communicate asynchronously through events, reducing coupling and improving scalability. Events represent significant changes in the system, such as a user registration or an order placement, and are processed by interested services.</p>
<h3 id="core-concepts">Core Concepts</h3>
<ul><li><strong>Events</strong>: Immutable records of something that happened (e.g., &quot;OrderCreated&quot;).</li><li><strong>Producers</strong>: Services that generate and publish events to a message broker.</li><li><strong>Consumers</strong>: Services that subscribe to and process events.</li><li><strong>Event Store</strong>: A persistent storage system (like Kafka) for events.</li><li><strong>Message Broker</strong>: A system that routes events between producers and consumers.</li></ul>
<h3 id="benefits">Benefits</h3>
<ol><li><strong>Loose Coupling</strong>: Services communicate via events, not direct API calls, reducing dependencies.</li><li><strong>Scalability</strong>: Producers and consumers can scale independently.</li><li><strong>Resilience</strong>: Asynchronous processing ensures the system remains operational if a service fails.</li><li><strong>Auditability</strong>: Events provide a historical record of system activities.</li><li><strong>Flexibility</strong>: New consumers can be added without modifying producers.</li></ol>
<h3 id="real-world-use-case">Real-World Use Case</h3>
<p>Consider an e-commerce platform where an order placement triggers multiple actions: updating inventory, sending a confirmation email, and processing payment. In a traditional synchronous system, these actions would be tightly coupled, increasing complexity. With EDA, the order service publishes an &quot;OrderCreated&quot; event, and independent services (inventory, notification, payment) consume it asynchronously, improving scalability and fault tolerance.</p>
<h2 id="apache-kafka-basics">Apache Kafka Basics</h2>
<p>Apache Kafka is a distributed streaming platform designed for high-throughput, fault-tolerant, and scalable event processing.</p>
<h3 id="key-components">Key Components</h3>
<ul><li><strong>Topics</strong>: Categories where events are published (e.g., &quot;order-events&quot;).</li><li><strong>Partitions</strong>: Subdivisions of topics for parallel processing and scalability.</li><li><strong>Producers</strong>: Applications that send events to topics.</li><li><strong>Consumers</strong>: Applications that read events from topics.</li><li><strong>Brokers</strong>: Kafka servers that store and manage events.</li><li><strong>Consumer Groups</strong>: Groups of consumers that share the load of processing events.</li></ul>
<h3 id="setting-up-kafka-with-spring-boot">Setting Up Kafka with Spring Boot</h3>
<p>Add the necessary dependency to your <code>pom.xml</code>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.kafka&lt;/groupId&gt;
    &lt;artifactId&gt;spring-kafka&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>Configure Kafka in <code>application.yml</code>:</p>
<pre><code class="language-yaml">spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: notification-service
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: com.example.event
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer</code></pre>
<h2 id="spring-kafka-integration">Spring Kafka Integration</h2>
<p>Below is an example of integrating Kafka with Spring Boot to produce and consume events in an e-commerce system.</p>
<h3 id="event-definition">Event Definition</h3>
<p>Define event classes for serialization:</p>
<pre><code class="language-java">public class OrderCreatedEvent {
    private Long orderId;
    private Long customerId;
    private Double total;
    private Instant timestamp;

    // Constructors, getters, and setters
    public OrderCreatedEvent(Long orderId, Long customerId, Double total, Instant timestamp) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.total = total;
        this.timestamp = timestamp;
    }
}</code></pre>
<h3 id="producing-events">Producing Events</h3>
<p>Create a service to publish events when an order is created:</p>
<pre><code class="language-java">import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderEventProducer {
    private static final Logger logger = LoggerFactory.getLogger(OrderEventProducer.class);
    private final KafkaTemplate&lt;String, Object&gt; kafkaTemplate;

    public OrderEventProducer(KafkaTemplate&lt;String, Object&gt; kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publishOrderCreated(Order order) {
        OrderCreatedEvent event = new OrderCreatedEvent(
                order.getId(),
                order.getCustomerId(),
                order.getTotal(),
                Instant.now()
        );
        kafkaTemplate.send(&quot;order-events&quot;, order.getId().toString(), event)
                .addCallback(
                        result -&gt; logger.info(&quot;Sent order event for order ID: {}&quot;, order.getId()),
                        ex -&gt; logger.error(&quot;Failed to send order event: {}&quot;, ex.getMessage())
                );
    }
}</code></pre>
<h3 id="consuming-events">Consuming Events</h3>
<p>Create a consumer to process order events:</p>
<pre><code class="language-java">import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventConsumer {
    private static final Logger logger = LoggerFactory.getLogger(OrderEventConsumer.class);
    private final NotificationService notificationService;

    public OrderEventConsumer(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;notification-service&quot;)
    public void handleOrderCreated(OrderCreatedEvent event) {
        logger.info(&quot;Received order created event for order ID: {}&quot;, event.getOrderId());
        notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
    }
}</code></pre>
<h3 id="order-service-integration">Order Service Integration</h3>
<p>Integrate the event producer into the order service:</p>
<pre><code class="language-java">import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class OrderService {
    private final OrderRepository orderRepository;
    private final OrderEventProducer eventProducer;

    public OrderService(OrderRepository orderRepository, OrderEventProducer eventProducer) {
        this.orderRepository = orderRepository;
        this.eventProducer = eventProducer;
    }

    public Order createOrder(Order order) {
        Order savedOrder = orderRepository.save(order);
        eventProducer.publishOrderCreated(savedOrder);
        return savedOrder;
    }
}</code></pre>
<p><strong>Entity</strong>:</p>
<pre><code class="language-java">import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = &quot;orders&quot;)
public class Order {
    @Id
    private Long id;
    private Long customerId;
    private Double total;
    private String status;

    // Constructors, getters, and setters
}</code></pre>
<p><strong>Repository</strong>:</p>
<pre><code class="language-java">import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository&lt;Order, Long&gt; {
    List&lt;Order&gt; findByCustomerId(Long customerId);
}</code></pre>
<p><strong>Controller</strong>:</p>
<pre><code class="language-java">import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/orders&quot;)
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity&lt;Order&gt; createOrder(@RequestBody Order order) {
        Order createdOrder = orderService.createOrder(order);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdOrder);
    }
}</code></pre>
<h2 id="event-patterns">Event Patterns</h2>
<p>Event-driven systems leverage patterns like event sourcing, CQRS, and Saga to address complex requirements.</p>
<h3 id="event-sourcing">Event Sourcing</h3>
<p>Event sourcing stores the state of an application as a sequence of events, enabling auditability and state reconstruction.</p>
<pre><code class="language-java">import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderEventSourcingService {
    private final KafkaTemplate&lt;String, Object&gt; kafkaTemplate;

    public OrderEventSourcingService(KafkaTemplate&lt;String, Object&gt; kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void saveOrderEvent(OrderEvent event) {
        kafkaTemplate.send(&quot;order-state-events&quot;, event.getOrderId().toString(), event);
    }
}

public class OrderEvent {
    private Long orderId;
    private String eventType; // e.g., CREATED, UPDATED, CANCELLED
    private Map&lt;String, Object&gt; payload;
    private Instant timestamp;

    // Constructors, getters, and setters
}</code></pre>
<h3 id="cqrs-command-query-responsibility-segregation">CQRS (Command Query Responsibility Segregation)</h3>
<p>CQRS separates read and write operations, optimizing performance for each.</p>
<pre><code class="language-java">@Service
public class OrderQueryService {
    private final OrderReadRepository readRepository;

    public OrderQueryService(OrderReadRepository readRepository) {
        this.readRepository = readRepository;
    }

    public OrderSummary getOrderSummary(Long orderId) {
        return readRepository.findSummaryById(orderId)
                .orElseThrow(() -&gt; new RuntimeException(&quot;Order summary not found&quot;));
    }
}

public interface OrderReadRepository extends JpaRepository&lt;OrderSummary, Long&gt; {
    Optional&lt;OrderSummary&gt; findSummaryById(Long orderId);
}

public class OrderSummary {
    private Long id;
    private Long customerId;
    private Double total;
    private String status;

    // Constructors, getters, and setters
}</code></pre>
<h3 id="saga-pattern">Saga Pattern</h3>
<p>The Saga pattern manages distributed transactions across microservices using a series of local transactions.</p>
<pre><code class="language-java">@Service
public class OrderSagaOrchestrator {
    private final KafkaTemplate&lt;String, Object&gt; kafkaTemplate;

    public OrderSagaOrchestrator(KafkaTemplate&lt;String, Object&gt; kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void startOrderSaga(Order order) {
        OrderSagaEvent event = new OrderSagaEvent(order.getId(), &quot;ORDER_CREATED&quot;, order);
        kafkaTemplate.send(&quot;order-saga&quot;, order.getId().toString(), event);
    }

    @KafkaListener(topics = &quot;order-saga&quot;, groupId = &quot;saga-orchestrator&quot;)
    public void handleSagaEvent(OrderSagaEvent event) {
        switch (event.getStatus()) {
            case &quot;ORDER_CREATED&quot;:
                // Trigger inventory check
                kafkaTemplate.send(&quot;inventory-saga&quot;, event.getOrderId().toString(), event);
                break;
            case &quot;INVENTORY_CONFIRMED&quot;:
                // Trigger payment processing
                kafkaTemplate.send(&quot;payment-saga&quot;, event.getOrderId().toString(), event);
                break;
            case &quot;PAYMENT_COMPLETED&quot;:
                // Finalize order
                completeOrderSaga(event.getOrderId());
                break;
        }
    }
}</code></pre>
<h2 id="error-handling">Error Handling</h2>
<p>Robust error handling ensures reliability in event-driven systems.</p>
<h3 id="dead-letter-queue-dlq">Dead Letter Queue (DLQ)</h3>
<p>Handle failed events by sending them to a DLQ for later analysis.</p>
<pre><code class="language-java">@Component
public class OrderEventConsumer {
    private final KafkaTemplate&lt;String, Object&gt; kafkaTemplate;

    public OrderEventConsumer(KafkaTemplate&lt;String, Object&gt; kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;notification-service&quot;)
    public void handleOrderCreated(ConsumerRecord&lt;String, Object&gt; record) {
        try {
            OrderCreatedEvent event = (OrderCreatedEvent) record.value();
            notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
        } catch (Exception e) {
            kafkaTemplate.send(&quot;order-events-dlq&quot;, record.key(), record.value());
            logger.error(&quot;Failed to process event, sent to DLQ: {}&quot;, e.getMessage());
        }
    }
}</code></pre>
<h3 id="retry-mechanism">Retry Mechanism</h3>
<p>Configure retries for transient failures:</p>
<pre><code class="language-java">@Component
public class OrderEventConsumer {
    private static final Logger logger = LoggerFactory.getLogger(OrderEventConsumer.class);

    @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;notification-service&quot;,
                   errorHandler = &quot;kafkaListenerErrorHandler&quot;)
    public void handleOrderCreated(OrderCreatedEvent event) {
        // Process event
        notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
    }

    @Bean
    public KafkaListenerErrorHandler kafkaListenerErrorHandler() {
        return (message, exception) -&gt; {
            logger.error(&quot;Retrying event processing: {}&quot;, message.getPayload());
            throw new RetryableException(&quot;Retrying event processing&quot;, exception.getCause());
        };
    }
}</code></pre>
<p><strong>Retry Configuration</strong> (in <code>application.yml</code>):</p>
<pre><code class="language-yaml">spring:
  kafka:
    listener:
      retry:
        max-attempts: 3
        initial-interval: 1000
        multiplier: 2</code></pre>
<h2 id="monitoring-and-observability">Monitoring and Observability</h2>
<p>Monitor Kafka clusters and Spring Boot applications to ensure performance and reliability.</p>
<h3 id="kafka-monitoring">Kafka Monitoring</h3>
<p>Use tools like Confluent Control Center or Prometheus with Kafka Exporter.</p>
<p><strong>Prometheus Configuration</strong> (in <code>application.yml</code>):</p>
<pre><code class="language-yaml">management:
  endpoints:
    web:
      exposure:
        include: prometheus, health, info</code></pre>
<p><strong>Prometheus Scrape Config</strong> (<code>prometheus.yml</code>):</p>
<pre><code class="language-yaml">scrape_configs:
  - job_name: 'order-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['order-service:8082']</code></pre>
<h3 id="centralized-logging">Centralized Logging</h3>
<p>Aggregate logs using ELK Stack or Fluentd:</p>
<pre><code class="language-java">import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class LogService {
    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    public void logEvent(String eventType, String details) {
        logger.info(&quot;Event processed: type={}, details={}&quot;, eventType, details);
    }
}</code></pre>
<h2 id="best-practices">Best Practices</h2>
<ol><li><strong>Event Schema Design</strong>:</li></ol>
<ul><li>Use clear, self-descriptive event schemas with versioning.</li><li>Example:</li></ul>
<pre><code class="language-java">     public class OrderEvent {
         private String version = &quot;1.0&quot;;
         private String eventType;
         private Map&lt;String, Object&gt; payload;

         // Constructors, getters, and setters
     }</code></pre>
<ol><li><strong>Idempotent Consumers</strong>:</li></ol>
<ul><li>Ensure consumers can handle duplicate events.</li><li>Example:</li></ul>
<pre><code class="language-java">     @Component
     public class IdempotentConsumer {
         private final Set&lt;String&gt; processedEvents = new HashSet&lt;&gt;();

         @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;notification-service&quot;)
         public void handleOrderCreated(OrderCreatedEvent event) {
             String eventId = event.getOrderId() + &quot;-&quot; + event.getTimestamp();
             if (processedEvents.contains(eventId)) {
                 logger.info(&quot;Duplicate event ignored: {}&quot;, eventId);
                 return;
             }
             processedEvents.add(eventId);
             notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
         }
     }</code></pre>
<ol><li><strong>Message Ordering</strong>:</li></ol>
<ul><li>Use partition keys to ensure order within a partition.</li><li>Example: Use <code>order.getId().toString()</code> as the key in <code>kafkaTemplate.send</code>.</li></ul>
<ol><li><strong>Monitoring and Alerts</strong>:</li></ol>
<ul><li>Set up alerts for consumer lag and broker health.</li><li>Use Kafka Exporter with Prometheus for metrics.</li></ul>
<ol><li><strong>Security</strong>:</li></ol>
<ul><li>Secure Kafka with SSL/TLS and SASL.</li><li>Example configuration:</li></ul>
<pre><code class="language-yaml">     spring:
       kafka:
         properties:
           security.protocol: SSL
           ssl.truststore.location: /path/to/truststore.jks
           ssl.truststore.password: password</code></pre>
<ol><li><strong>Testing</strong>:</li></ol>
<ul><li>Use Testcontainers for integration testing with Kafka.</li><li>Example:</li></ul>
<pre><code class="language-java">     @SpringBootTest
     @Testcontainers
     class OrderEventProducerTest {
         @Container
         private static final KafkaContainer kafka = new KafkaContainer(
             DockerImageName.parse(&quot;confluentinc/cp-kafka:7.3.0&quot;)
         );

         @Autowired
         private OrderEventProducer producer;

         @Test
         void shouldPublishOrderEvent() {
             Order order = new Order(1L, 100L, 99.99, &quot;PENDING&quot;);
             producer.publishOrderCreated(order);
             // Verify event in Kafka topic
         }
     }</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Event-driven architecture with Apache Kafka enables scalable, resilient, and loosely coupled systems. By integrating Kafka with Spring Boot, you can build robust applications that handle high-throughput event processing. Key takeaways:</p>
<ul><li>Use Kafka for asynchronous, event-driven communication.</li><li>Implement event sourcing, CQRS, and Saga patterns for complex workflows.</li><li>Handle errors with DLQs and retries.</li><li>Monitor Kafka clusters and Spring Boot services with Prometheus and centralized logging.</li><li>Follow best practices like idempotent consumers and secure configurations.</li></ul>
<p><strong>Call to Action</strong>: Start building your event-driven application using the examples above. Experiment with Kafka locally using Docker and scale to production with proper monitoring. Share your experiences or questions in the comments or on X to join the developer community!</p>]]></content:encoded>
    </item>
    <item>
      <title>Containerizing Spring Boot Applications with Docker</title>
      <link>https://dahalutsab.com.np/dispatches/containerizing-spring-boot-docker</link>
      <guid isPermaLink="true">https://dahalutsab.com.np/dispatches/containerizing-spring-boot-docker</guid>
      <pubDate>Mon, 25 Dec 2023 09:00:00 GMT</pubDate>
      <dc:creator>Utsab Dahal</dc:creator>
      <category>DevOps</category>
      <category>Docker</category>
      <category>Containerization</category>
      <category>Spring Boot</category>
      <category>DevOps</category>
      <category>Kubernetes</category>
      <description>Complete guide to containerizing Spring Boot applications using Docker, including multi-stage builds, Docker Compose, Kubernetes deployment, and DevOps best practices.</description>
      <content:encoded><![CDATA[<p>Docker revolutionizes application deployment by packaging Spring Boot applications with their dependencies into lightweight, portable containers. This comprehensive guide covers containerizing Spring Boot applications using Docker, including creating Dockerfiles, multi-stage builds, orchestrating with Docker Compose, deploying to Kubernetes, and following DevOps best practices. With practical examples and code snippets, you'll learn how to streamline your deployment pipeline for scalability and reliability.</p>
<h2 id="why-docker">Why Docker?</h2>
<p>Docker provides a consistent, portable, and efficient way to deploy Spring Boot applications, making it a cornerstone of modern DevOps practices.</p>
<h3 id="benefits">Benefits</h3>
<ul><li>Consistency: Ensures identical environments across development, testing, and production, eliminating &quot;it works on my machine&quot; issues.</li><li>Portability: Containers run on any system with Docker, from local machines to cloud providers.</li><li>Scalability: Easily scale applications horizontally by running multiple container instances.</li><li>Isolation: Each container runs in its own environment, preventing conflicts between applications.</li><li>Resource Efficiency: Containers are lightweight compared to virtual machines, optimizing resource usage.</li></ul>
<h3 id="docker-basics">Docker Basics</h3>
<ul><li>Image: A read-only template used to create containers, containing the application and its dependencies.</li><li>Container: A running instance of an image, isolated from the host system.</li><li>Dockerfile: A script with instructions to build a Docker image.</li><li>Registry: A repository (e.g., Docker Hub) for storing and distributing Docker images.</li></ul>
<h3 id="real-world-use-case">Real-World Use Case</h3>
<p>Imagine a Spring Boot-based e-commerce application with microservices for user management, order processing, and inventory. Without Docker, deploying these services across different environments risks configuration drift. Docker ensures each service runs in a consistent environment, simplifying deployment and scaling during high-traffic events like flash sales.</p>
<h2 id="creating-dockerfiles">Creating Dockerfiles</h2>
<p>A Dockerfile defines the steps to build a Docker image for your Spring Boot application. Below are examples of basic and optimized Dockerfiles.</p>
<h3 id="basic-dockerfile">Basic Dockerfile</h3>
<pre><code class="language-dockerfile">FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/myapp-1.0.0.jar app.jar

EXPOSE 8080

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre>
<p><strong>Explanation</strong>:</p>
<ul><li><code>FROM openjdk:17-jdk-slim</code>: Uses a lightweight OpenJDK 17 base image.</li><li><code>WORKDIR /app</code>: Sets the working directory inside the container.</li><li><code>COPY target/myapp-1.0.0.jar app.jar</code>: Copies the compiled JAR file.</li><li><code>EXPOSE 8080</code>: Declares the port the application uses.</li><li><code>ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code>: Runs the Spring Boot application.</li></ul>
<h3 id="optimized-dockerfile">Optimized Dockerfile</h3>
<pre><code class="language-dockerfile">FROM openjdk:17-jdk-slim

# Create non-root user for security
RUN addgroup --system spring &amp;&amp; adduser --system spring --ingroup spring

# Set working directory
WORKDIR /app

# Copy jar file
COPY target/myapp-1.0.0.jar app.jar

# Change ownership
RUN chown spring:spring app.jar

# Switch to non-root user
USER spring:spring

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# Run application
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre>
<p><strong>Improvements</strong>:</p>
<ul><li>Runs as a non-root user (<code>spring</code>) to enhance security.</li><li>Adds a health check using Spring Boot Actuator’s <code>/actuator/health</code> endpoint to monitor container health.</li><li>Properly sets ownership of the JAR file to avoid permission issues.</li></ul>
<h2 id="multi-stage-builds">Multi-stage Builds</h2>
<p>Multi-stage builds reduce image size and improve security by separating the build and runtime environments.</p>
<pre><code class="language-dockerfile"># Build stage
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Runtime stage
FROM openjdk:17-jdk-slim
RUN addgroup --system spring &amp;&amp; adduser --system spring --ingroup spring
WORKDIR /app
COPY --from=build /app/target/myapp-1.0.0.jar app.jar
RUN chown spring:spring app.jar
USER spring:spring
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre>
<p><strong>Benefits</strong>:</p>
<ul><li>Smaller Image Size: The build stage uses Maven to compile the application, but the runtime stage only includes the compiled JAR and OpenJDK.</li><li>Security: Excludes build tools (e.g., Maven) from the final image, reducing attack surface.</li><li>Efficiency: Skips tests during the build (<code>-DskipTests</code>) to speed up the process (enable tests in CI/CD for quality assurance).</li></ul>
<h2 id="docker-compose">Docker Compose</h2>
<p>Docker Compose simplifies orchestrating multiple containers, such as a Spring Boot application and a database.</p>
<h3 id="example-docker-compose-configuration">Example Docker Compose Configuration</h3>
<p>For an e-commerce application with a user service and MySQL database:</p>
<pre><code class="language-yaml">version: '3.8'
services:
  user-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - &quot;8080:8080&quot;
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://user-db:3306/user_db
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=password
      - SPRING_PROFILES_ACTIVE=prod
    depends_on:
      - user-db
    healthcheck:
      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;-f&quot;, &quot;http://localhost:8080/actuator/health&quot;]
      interval: 30s
      timeout: 3s
      retries: 3
  user-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=user_db
    ports:
      - &quot;3306:3306&quot;
    volumes:
      - user-db-data:/var/lib/mysql
    healthcheck:
      test: [&quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot;]
      interval: 30s
      timeout: 3s
      retries: 3
volumes:
  user-db-data:</code></pre>
<p><strong>Explanation</strong>:</p>
<ul><li>user-service: Builds the Spring Boot application from the Dockerfile.</li><li>user-db: Runs a MySQL database with a persistent volume for data storage.</li><li>Environment Variables: Configures the Spring Boot application to connect to MySQL.</li><li>Health Checks: Ensures both services are healthy before starting dependent services.</li><li>Volumes: Persists MySQL data to avoid data loss on container restart.</li></ul>
<p><strong>Running Docker Compose</strong>: docker-compose up -d This starts the services in detached mode. Access the application at http://localhost:8080.</p>
<h2 id="deployment-strategies">Deployment Strategies</h2>
<p>Deploying Dockerized Spring Boot applications to production requires robust strategies for scalability and reliability.</p>
<h3 id="deploying-to-kubernetes">Deploying to Kubernetes</h3>
<p>Kubernetes orchestrates containers for high availability and scalability.</p>
<p><strong>Deployment YAML</strong> (for User Service):</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: myregistry/user-service:1.0.0
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_DATASOURCE_URL
          value: &quot;jdbc:mysql://user-db:3306/user_db&quot;
        - name: SPRING_DATASOURCE_USERNAME
          value: &quot;root&quot;
        - name: SPRING_DATASOURCE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: password
        - name: SPRING_PROFILES_ACTIVE
          value: &quot;prod&quot;
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5</code></pre>
<p><strong>Service YAML</strong> (for User Service):</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP</code></pre>
<p><strong>MySQL Deployment YAML</strong>:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-db
spec:
  selector:
    matchLabels:
      app: user-db
  template:
    metadata:
      labels:
        app: user-db
    spec:
      containers:
      - name: user-db
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: password
        - name: MYSQL_DATABASE
          value: user_db
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-data
        persistentVolumeClaim:
          claimName: mysql-pvc</code></pre>
<p><strong>Persistent Volume Claim (PVC)</strong>:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi</code></pre>
<p><strong>Secrets</strong> (create with kubectl):</p>
<p>kubectl create secret generic mysql-secrets --from-literal=password=password</p>
<p><strong>Deploying to Kubernetes</strong>: kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f mysql-deployment.yaml kubectl apply -f mysql-pvc.yaml</p>
<h3 id="ci-cd-integration">CI/CD Integration</h3>
<p>Integrate Docker builds into a CI/CD pipeline using GitHub Actions:</p>
<pre><code class="language-yaml">name: Build and Push Docker Image

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
    - name: Build with Maven
      run: mvn clean package -DskipTests
    - name: Log in to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: myregistry/user-service:1.0.0</code></pre>
<p>This pipeline builds the Spring Boot application, creates a Docker image, and pushes it to a registry.</p>
<h2 id="monitoring-and-logging">Monitoring and Logging</h2>
<p>Monitoring and logging ensure the reliability of Dockerized applications in production.</p>
<h3 id="spring-boot-actuator">Spring Boot Actuator</h3>
<p>Enable Actuator for health and metrics:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p><strong>application.yml</strong>:</p>
<pre><code class="language-yaml">management:
  endpoints:
    web:
      exposure:
        include: health, metrics, prometheus</code></pre>
<h3 id="prometheus-monitoring">Prometheus Monitoring</h3>
<p>Scrape metrics from the /actuator/prometheus endpoint:</p>
<pre><code class="language-yaml">scrape_configs:
  - job_name: 'user-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['user-service:8080']</code></pre>
<h3 id="centralized-logging-with-elk-stack">Centralized Logging with ELK Stack</h3>
<p>Configure Logback to send logs to an ELK Stack:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;net.logstash.logback&lt;/groupId&gt;
    &lt;artifactId&gt;logstash-logback-encoder&lt;/artifactId&gt;
    &lt;version&gt;7.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p><strong>logback-spring.xml</strong>:</p>
<pre><code class="language-xml">&lt;configuration&gt;
    &lt;appender name=&quot;LOGSTASH&quot; class=&quot;net.logstash.logback.appender.LogstashTcpSocketAppender&quot;&gt;
        &lt;destination&gt;logstash:5000&lt;/destination&gt;
        &lt;encoder class=&quot;net.logstash.logback.encoder.LogstashEncoder&quot;/&gt;
    &lt;/appender&gt;
    &lt;root level=&quot;INFO&quot;&gt;
        &lt;appender-ref ref=&quot;LOGSTASH&quot;/&gt;
    &lt;/root&gt;
&lt;/configuration&gt;</code></pre>
<p><strong>Docker Compose for ELK</strong>:</p>
<pre><code class="language-yaml">version: '3.8'
services:
  logstash:
    image: docker.elastic.co/logstash/logstash:8.5.0
    ports:
      - &quot;5000:5000&quot;
    environment:
      - xpack.monitoring.enabled=false
    volumes:
      - ./logstash-pipeline:/usr/share/logstash/pipeline
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - &quot;9200:9200&quot;
  kibana:
    image: docker.elastic.co/kibana/kibana:8.5.0
    ports:
      - &quot;5601:5601&quot;
    depends_on:
      - elasticsearch</code></pre>
<h2 id="best-practices">Best Practices</h2>
<ul><li>Minimize Image Size:</li><li>Use slim base images (e.g., openjdk:17-jdk-slim).</li><li>Leverage multi-stage builds to exclude build tools.</li><li>Example:</li></ul>
<pre><code class="language-dockerfile">    RUN rm -rf /app/src</code></pre>
<ul><li>Security:</li><li>Run containers as non-root users.</li><li>Scan images for vulnerabilities using Trivy:</li></ul>
<p>trivy image myregistry/user-service:1.0.0</p>
<ul><li>Health Checks:</li><li>Implement health checks in Dockerfiles and Kubernetes probes.</li><li>Example:</li></ul>
<pre><code class="language-yaml">    livenessProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 10</code></pre>
<ul><li>Environment Configuration:</li><li>Use environment variables for configuration.</li><li>Example:</li></ul>
<pre><code class="language-yaml">    env:
      - name: SPRING_DATASOURCE_URL
        value: &quot;jdbc:mysql://user-db:3306/user_db&quot;</code></pre>
<ul><li>Logging:</li><li>Configure structured JSON logging:</li></ul>
<pre><code class="language-java">    @Component
    public class LogService {
        private static final Logger logger = LoggerFactory.getLogger(LogService.class);

        public void logAction(String action) {
            logger.info(&quot;{\&quot;action\&quot;: \&quot;{}\&quot;, \&quot;timestamp\&quot;: \&quot;{}\&quot;}&quot;, action, Instant.now());
        }
    }</code></pre>
<ul><li>CI/CD Integration:</li><li>Automate image building and deployment.</li><li>Use versioned tags (e.g., myregistry/user-service:1.0.0).</li></ul>
<ul><li>Resource Limits:</li><li>Set CPU and memory limits in Kubernetes:</li></ul>
<pre><code class="language-yaml">    resources:
      limits:
        cpu: &quot;0.5&quot;
        memory: &quot;512Mi&quot;
      requests:
        cpu: &quot;0.2&quot;
        memory: &quot;256Mi&quot;</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Containerizing Spring Boot applications with Docker enables consistent, portable, and scalable deployments. By using multi-stage builds, Docker Compose, and Kubernetes, you can streamline your DevOps pipeline. Key takeaways:</p>
<ul><li>Create efficient Dockerfiles with multi-stage builds.</li><li>Use Docker Compose for local development and testing.</li><li>Deploy to Kubernetes for production-grade scalability.</li><li>Monitor containers with Prometheus and centralize logs with ELK.</li><li>Follow best practices like running as non-root and automating CI/CD.</li></ul>
<p><strong>Call to Action</strong>: Start containerizing your Spring Boot application using the provided examples. Deploy locally with Docker Compose and scale to production with Kubernetes. Share your feedback or questions in the comments or on X to join the developer community!</p>]]></content:encoded>
    </item>
  </channel>
</rss>
