DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Microservices

A microservices architecture is a development method for designing applications as modular services that seamlessly adapt to a highly scalable and dynamic environment. Microservices help solve complex issues such as speed and scalability, while also supporting continuous testing and delivery. This Zone will take you through breaking down the monolith step by step and designing a microservices architecture from scratch. Stay up to date on the industry's changes with topics such as container deployment, architectural design patterns, event-driven architecture, service meshes, and more.

icon
Latest Premium Content
Trend Report
Cloud Native
Cloud Native
Trend Report
Modern API Management
Modern API Management
Refcard #379
Getting Started With Serverless Application Architecture
Getting Started With Serverless Application Architecture

DZone's Featured Microservices Resources

Kubernetes v1.34: Enabling Smarter Traffic Routing With PreferSameNode and PreferSameZone

Kubernetes v1.34: Enabling Smarter Traffic Routing With PreferSameNode and PreferSameZone

By Nitin Ware
Kubernetes has steadily evolved into an industry standard for container orchestration, powering platforms from small developer clusters to hyperscale AI and data infrastructures. Every new release introduces features that not only make workloads easier to manage but also improve performance, cost efficiency, and resilience. With the v1.34 release, one of the standout enhancements is the introduction of traffic distribution preferences for Kubernetes Services. Specifically: PreferSameNode: route traffic to the same node as the client pod if possible.PreferSameZone: routing traffic by giving preference to endpoints in the same topology zone before going for cross-zone. These policies add smarter, locality-aware routing to Service traffic distribution. Instead of treating all pods equally, Kubernetes can now prefer pods that are closer to the client, whether on the same node or in the same availability zone (AZ). This change is simple, but it has meaningful implications for performance-sensitive and cost-sensitive workloads, particularly in large multi-node and multi-zone clusters. Traffic Distribution Significance Traditionally, a Kubernetes Service balances traffic evenly across all endpoints/pods that match its selector. This even traffic distribution is simple, predictable, and works well for most use cases. However, it does not take into consideration topology, the physical or logical placement of pods across nodes and zones. Round-Robin Challenges Increased latency: If a client pod on Node A routes to a Service endpoint on Node B (or worst case to a different zone), the extra network hop adds milliseconds of delay.Cross-zone costs: In cloud environments, cross-az traffic is often billed by cloud providers; even a few mb's of cross-zone traffic across thousands of pods can rack up significant costs.Cache inefficiency: Some ML inference services cache models in memory per pod. If requests bounce across pods randomly, cache hit rates increase, hurting both performance and resource efficiency. What’s New in Kubernetes v1.34 The new trafficDistribution field, Kubernetes services now support an optional field under spec: Shell spec: trafficDistribution: PreferSameNode | PreferSameZone Default behavior (if unset): traffic is still distributed evenly across all endpoints.PreferSameNode: The kube-proxy (or service proxy) will attempt to send traffic to pods running on the same node as the client pod. If no such endpoints are available, it falls back to zone-level or cluster-wide balancing.PreferSameZone: The proxy will prioritize endpoints within the same topology zone as the client pod. If none are available, it falls back to cluster-wide distribution. Traffic Distribution High-Level Diagram These preferences are optional, and if no preference is specified, then by default, the traffic will be evenly distributed across all endpoints in the cluster. Benefits Lower latency: Requests take fewer network hops when served locally on the same node or within the same zone. This is especially critical for microservices with low SLA requirements or ML workloads where inference times are measured in milliseconds.Reduced costs: Cloud providers typically charge for cross-zone traffic. Routing to local pods first avoids these charges unless necessary.Improved cache utilization: Workloads such as ML inference pods often keep models, embeddings, or feature stores warm in memory, with the same node routing, which increases cache hit rates.Built-in fault tolerance: Both policies are preferences, not hard requirements. If no local endpoints exist due to a node being drained or a zone outage, then Kubernetes seamlessly falls back to cluster-wide distribution. Use Cases ML inference services cache warm models in the pod.Distributed systems where data nodes align with zones.Larger orgs deploying across multiple AZs can achieve smart failover as traffic stays local under normal conditions, but failover seamlessly if the zone experiences an outage. Demo Walkthrough We will try to cover traffic distribution scenarios — default, PreferSameZone, PreferSameNode, and fallback — in the demo below. Demo: Set Up Cluster, Deploy Pods, Services, and Client Step 1: Start a multi-node cluster on minikube and label the nodes with zones: Shell minikube start -p mnode --nodes=3 --kubernetes-version=v1.34.0 kubectl config use-context mnode kubectl label node mnode-m02 topology.kubernetes.io/zone=zone-a --overwrite kubectl label node mnode-m03 topology.kubernetes.io/zone=zone-b --overwrite Step 2: Deploy the echo app with two replicas and the echo service. Shell # echo-pod.yaml apiVersion: apps/v1 kind: Deployment metadata: name: echo spec: replicas: 2 selector: matchLabels: app: echo template: metadata: labels: app: echo spec: containers: - name: echo image: hashicorp/http-echo args: - "-text=Hello from $(POD_NAME)" env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name ports: - containerPort: 5678 # echo-service.yaml apiVersion: v1 kind: Service metadata: name: echo-svc spec: selector: app: echo ports: - port: 80 targetPort: 5678 Shell kubectl apply -f echo-pod.yaml kubectl apply -f echo-service.yaml # verify pods are running on separate nodes and zones kubectl get pods -l app=echo -o=custom-columns=NAME:.metadata.name,NODE:.spec.nodeName --no-headers \ | while read pod node; do zone=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}') printf "%-35s %-15s %s\n" "$pod" "$node" "$zone" done As you can see in the screenshot below, two echo pods spin up on separate nodes (mnode-m02, mnode-m03) and availability zones (zone-a, zone-b). Step 3: Deploy a client pod in zone A. YAML # client.yaml apiVersion: v1 kind: Pod metadata: name: client spec: nodeSelector: topology.kubernetes.io/zone: zone-a restartPolicy: Never containers: - name: client image: alpine:3.19 command: ["sh", "-c", "sleep infinity"] stdin: true tty: true Shell kubectl apply -f client.yaml kubectl get pod client -o=custom-columns=NAME:.metadata.name,NODE:.spec.nodeName --no-headers \ | while read pod node; do zone=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}') printf "%-35s %-15s %s\n" "$pod" "$node" "$zone" done Client pod is scheduled on node mnode-m02 in zone-a. Step 4: Set up a helper script in the client pod. Shell kubectl exec -it client -- sh apk add --no-cache curl jq cat > /hit.sh <<'EOS' #!/bin/sh COUNT="${1:-20}" SVC="${2:-echo}" PORT="${3:-80}" i=1 while [ "$i" -le "$COUNT" ]; do curl -s "http://${SVC}:${PORT}/" \ | jq -r '.env.POD_NAME + "@" + .env.NODE_NAME' i=$((i+1)) done | sort | uniq -c EOS chmod +x /hit.sh exit Demo: Default Behavior Form client shell run script: hit.sh to generate traffic from client pod to echo service. Shell /hit.sh 20 Behavior: In the below screenshot, you can see traffic routed to both pods (10 requests each) in round-robin style. Demo: PreferSameNode Patch echo service definition spec to add/patch traffic distribution: PreferSameNode. Shell kubectl patch svc echo --type merge -p '{"spec":{"trafficDistribution":"PreferSameNode"}' Form client shell run script:hit.sh to generate traffic from client pod to echo service. Shell /hit.sh 40 Behavior: Traffic should get routed to pod:echo-687cbdc966-mgwn5@mnode-m02 residing on the same node:mnode-m02 as client pod. Demo: PreferSameZone Update echo service definition spec to add/patch traffic distribution: PreferSameNode Shell kubectl patch svc echo --type merge -p '{"spec":{"trafficDistribution":"PreferSameZone"}' Form client shell run script:hit.sh to generate traffic from client pod to echo service. Shell /hit.sh 40 Behavior: traffic should get routed to the pod residing in the same zone (zone-a) as the client pod. Demo: Fallback Force all echo pods to zone-b, then test again: Shell kubectl scale deploy echo --replicas=1 kubectl patch deploy echo --type merge -p \ '{"spec":{"template":{"spec":{"nodeSelector":{"topology.kubernetes.io/zone":"zone-b"}}}' kubectl rollout status deploy/echo Form client shell run script:hit.sh to generate traffic from client pod to echo service. Result Summary policybehavior Default Traffic distributed across all endpoints in round-robin fashion. PreferSameNode Prefers pods on the same node, falls back if none available. PreferSameZone Prefers pods in the same zone, falls back if none available. Conclusion Kubernetes release v1.34 adds two small but impactful capabilities: PreferSameNode and PreferSameZone, these preferences helps developers and k8s operators to make traffic routing smarter, ensuring traffic prioritizes local endpoints while maintaining resiliency with fallback mechanism. References PreferClose traffic distribution is deprecated More
Detecting Supply Chain Attacks in NPM, PyPI, and Docker: Real-World Techniques That Work

Detecting Supply Chain Attacks in NPM, PyPI, and Docker: Real-World Techniques That Work

By David Iyanu Jonathan
The digital ecosystem breathes through trust. Every npm install, every pip install, every docker pull represents a leap of faith — a developer placing confidence in code written by strangers, maintained by volunteers, distributed through systems they've never seen. This trust, however, has become the Achilles' heel of modern software development. Supply chain attacks don't knock on your front door. They slip through the dependencies you invited in yourself. The Rising Threat: When Trust Becomes Vulnerability Software supply chain attacks represent a paradigm shift in cybersecurity threats. Rather than directly targeting fortified systems, attackers have discovered something far more insidious: contaminating the very building blocks developers use to construct their applications. The math is simple, yet terrifying — compromise one widely-used package, and suddenly you have access to thousands of downstream applications. Consider the numbers. A typical Node.js application might depend on 400+ packages. Each package brings its own dependencies. The dependency tree explodes exponentially, creating what security researchers call "dependency hell" — not just for version conflicts, but for attack surface expansion. The SolarWinds breach of 2020 demonstrated this with devastating clarity. Attackers didn't need to penetrate 18,000 organizations individually. They contaminated a single software update, then watched as victims essentially installed the malware themselves. Trust became the delivery mechanism. Understanding the Modern Attack Surface Today's software supply chain resembles a complex ecosystem where multiple attack vectors converge. Dependency confusion attacks exploit naming similarities between public and private repositories. Typosquatting campaigns target developers' muscle memory — urlib instead of urllib, beuatifulsoup instead of beautifulsoup. These aren't accidental typos; they're calculated traps. Malicious maintainers represent perhaps the most concerning vector. Package maintainers often work for free, maintaining critical infrastructure used by millions. Burnout is common. Account takeovers happen. Sometimes legitimate maintainers sell their packages to bad actors, who then inject malicious code into trusted libraries. The attack surface extends beyond individual packages. CI/CD pipelines themselves become targets, with attackers compromising build systems to inject malicious code during the compilation process. Container registries face similar threats — malicious Docker images masquerading as legitimate base images, complete with backdoors baked into the filesystem. NPM: Securing the JavaScript Ecosystem JavaScript's package ecosystem moves fast. Really fast. The NPM registry hosts over two million packages, with thousands added daily. This velocity creates opportunities for both innovation and exploitation. Native tooling provides your first line of defense. The npm audit command, built into NPM itself, scans your dependency tree against known vulnerability databases. Simple to use: npm audit reveals vulnerabilities, while npm audit fix attempts automatic remediation. But automation isn't always wise — major version jumps can break your application. Shell bashnpm audit --audit-level high npm audit --production # Focus on production dependencies only Socket.dev has emerged as a game-changer for NPM security. Unlike traditional vulnerability scanners that look for known CVEs, Socket analyzes package behavior. Does this utility package really need network access? Why is a string manipulation library spawning child processes? Socket's behavioral analysis catches malicious packages before they're widely known as threats. The tool integrates seamlessly with GitHub pull requests, automatically flagging suspicious dependency changes. Install their GitHub app, and Socket will comment on PRs when new dependencies exhibit concerning behaviors — filesystem access, network calls, shell execution. It's like having a security expert review every dependency addition. Snyk operates differently — comprehensive, enterprise-focused, battle-tested. Their database combines public vulnerability information with proprietary research. Snyk doesn't just find vulnerabilities; it provides context. Risk scores, exploit maturity, and fix guidance. The CLI tool integrates into any workflow: Shell bashsnyk test # Test current project snyk monitor # Continuous monitoring snyk wizard # Interactive fixing GitHub's Dependabot represents automation at scale. Enabled by default for public repositories, Dependabot monitors your dependencies and automatically creates pull requests when updates fix security issues. The key insight: automate the mundane, but review the critical. The event-stream incident serves as a cautionary tale. In 2018, the maintainer of event-stream — a popular Node.js package with millions of weekly downloads — transferred ownership to a seemingly legitimate user. The new maintainer added a malicious dependency that specifically targeted the Copay cryptocurrency wallet. The attack was surgical: the malicious code only activated when it detected that it was running within the Copay application. This incident highlighted how trust chains can be exploited through social engineering and legitimate-seeming account transfers. PyPI: Python's Package Security Landscape Python's Package Index faces unique challenges. The language's popularity in data science, machine learning, and automation means PyPI packages often handle sensitive data. Scientific computing libraries deal with massive datasets, financial modeling packages process trading algorithms, and DevOps tools manage infrastructure credentials. pip-audit, developed by PyPA (Python Packaging Authority), brings vulnerability scanning directly to Python developers. Unlike pip's basic functionality, pip-audit specifically focuses on security. It cross-references your installed packages against the OSV (Open Source Vulnerabilities) database and PyUp.io's safety database. Shell bashpip-audit # Audit current environment pip-audit --requirement requirements.txt pip-audit --format json # Machine-readable output The tool's strength lies in its integration with Python's ecosystem. It understands virtual environments, requirements files, and poetry.lock files. Critical for CI/CD integration, where you need consistent, reproducible security scanning. Bandit complements pip-audit by focusing on code analysis rather than dependency scanning. While pip-audit finds vulnerable packages, Bandit identifies vulnerable code patterns within your own codebase. Hard-coded passwords, SQL injection patterns, unsafe deserialization — Bandit catches what automated dependency scanners miss. OSV Scanner represents Google's contribution to open-source security. The tool doesn't just scan Python packages; it's language-agnostic, supporting NPM, PyPI, Go modules, and more. What makes OSV Scanner special is its data source: the OSV database aggregates vulnerability information from multiple sources, providing comprehensive coverage often missing from single-vendor solutions. Typosquatting campaigns in PyPI demonstrate the creativity of attackers. Research has identified thousands of malicious packages with names deliberately similar to popular libraries. urllib becomes urlib, requests becomes request, tensorflow becomes tensorfow. These packages often contain information-stealing malware designed to harvest environment variables, SSH keys, and authentication tokens. The Python Package Index has responded by implementing typosquatting protections and requiring two-factor authentication for critical package maintainers. However, the fundamental challenge remains: how do you balance accessibility with security in an ecosystem built on trust? Docker Images: Container Security in Practice Container security extends far beyond scanning individual images. The entire container lifecycle — from base images to runtime — presents attack opportunities. Malicious base images, vulnerable dependencies baked into containers, secrets accidentally included in layers, and runtime privilege escalation. Trivy has become the gold standard for container vulnerability scanning. Developed by Aqua Security and open-sourced, Trivy scans not just the final container image but individual layers, understanding how vulnerabilities propagate through the Docker build process. Shell bashtrivy image nginx:latest trivy image --severity HIGH,CRITICAL ubuntu:20.04 trivy filesystem --security-checks vuln,config . Trivy's comprehensive approach examines OS packages, language-specific dependencies (NPM, PyPI, Go modules), and configuration issues. It understands Dockerfiles, Kubernetes manifests, and Terraform configurations. The tool provides actionable remediation advice — not just "vulnerability exists" but "upgrade to version X" or "use this alternative base image." Grype, developed by Anchore, focuses specifically on vulnerability detection with impressive speed. Where some scanners take minutes to analyze large images, Grype typically completes scans in seconds. The performance advantage becomes critical in CI/CD pipelines where scan time directly impacts deployment velocity. Docker Scout, Docker's native security solution, integrates directly into Docker Desktop and Docker Hub. The integration advantage is significant — Scout automatically scans images as you build them, providing immediate feedback without requiring separate tool installation or configuration. Consider this vulnerable Dockerfile example: Dockerfile dockerfileFROM ubuntu:18.04 RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ npm COPY requirements.txt . RUN pip3 install -r requirements.txt COPY package.json . RUN npm install COPY . . EXPOSE 8080 CMD ["python3", "app.py"] Scanning this with Trivy reveals multiple issues: Ubuntu 18.04 contains numerous CVEs, pip packages might have vulnerabilities, npm dependencies could be compromised, and the image runs as root by default. Each issue represents a potential attack vector. Base image trust becomes crucial. Official images from Docker Hub generally receive regular security updates. Third-party images vary wildly in maintenance quality. Alpine Linux has gained popularity partly due to its minimal attack surface — fewer packages mean fewer vulnerabilities. However, Alpine's use of musl libc instead of glibc can cause compatibility issues with some applications. CI/CD Pipeline Integration: Automation Without Compromise Security scanning integrated into CI/CD pipelines transforms reactive security into proactive defense. Rather than discovering vulnerabilities in production, you catch them during development. The key principle: fail fast, fail early, fail safely. GitHub Actions provides an ideal platform for security automation. The ecosystem includes pre-built actions for most security tools, reducing configuration complexity. Here's a comprehensive security scanning workflow: YAML yamlname: Security Scan on: [push, pull_request] jobs: security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: NPM Audit run: npm audit --audit-level high - name: Snyk Security Scan uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN } - name: Docker Security Scan run: | docker build -t myapp . trivy image --exit-code 1 --severity HIGH,CRITICAL myapp Security gates require careful calibration. Failing to address every medium-severity vulnerability might sound secure, but it can paralyze development. Teams often start with critical and high-severity issues, gradually tightening thresholds as their security posture improves. The challenge lies in balancing security with velocity. Automated fixes work well for straightforward updates but can break functionality for major version changes. Many teams implement a hybrid approach: automatic updates for patch releases, manual review for minor updates, and extensive testing for major version changes. GitLab CI provides similar capabilities with its built-in security scanning. GitLab's advantage lies in integration — dependency scanning, container scanning, and static analysis are built into the platform, requiring minimal configuration for basic security coverage. Monitoring and Incident Response: Beyond Prevention Prevention isn't enough. Even with comprehensive scanning, zero-day vulnerabilities appear regularly. Newly disclosed security issues affect packages you've already vetted and deployed. Effective security requires ongoing monitoring and rapid response capabilities. OSV.dev serves as a centralized vulnerability database aggregating information from multiple sources. Unlike vendor-specific databases, OSV provides a unified API for querying vulnerabilities across ecosystems. This aggregation enables comprehensive monitoring — you can track all vulnerabilities affecting your technology stack from a single source. Have I Been Pwned extends beyond personal email monitoring. The service now includes domain monitoring for organizations, alerting when employee credentials appear in data breaches. Since many supply chain attacks begin with compromised developer accounts, monitoring for credential exposure provides early warning of potential threats. Malicious package repositories track known bad packages across ecosystems. The Python Advisory Database, npm's security advisories, and similar resources provide structured information about malicious packages. Automating checks against these databases helps identify whether your dependencies have been flagged as malicious. When you discover a compromised dependency in your stack, response speed matters. Document your incident response process before you need it: Immediate containment: Stop deployments, isolate affected systemsImpact assessment: Identify what data the compromised package could accessRemediation: Update to safe versions, scan for indicators of compromiseRecovery verification: Ensure complete removal of malicious codePost-incident review: Update processes to prevent similar issues The xz-utils backdoor of 2024 demonstrated the sophistication of modern supply chain attacks. Attackers spent years building trust within the project community, gradually gaining maintainer access. The backdoor was nearly undetectable, hidden within binary test files and activated only under specific conditions. This attack highlighted how traditional scanning tools might miss sophisticated attacks that don't manifest as obvious vulnerabilities. Practical Implementation: Starting Your Security Journey Beginning comprehensive supply chain security can feel overwhelming. Start small, build incrementally, and focus on high-impact changes first. Week 1: Visibility Run npm audit, pip-audit, and trivy on your main applicationsDocument current vulnerability exposureIdentify critical issues requiring immediate attention Week 2: Basic Automation Enable GitHub Dependabot or equivalent automated dependency updatesAdd basic security scanning to your CI/CD pipelineConfigure notifications for critical vulnerabilities Week 3: Enhanced Scanning Integrate behavioral analysis tools like Socket.dev for NPM packagesAdd container scanning to your Docker build processImplement security gates for critical vulnerabilities Week 4: Monitoring and Response Set up OSV.dev monitoring for your technology stackCreate incident response procedures for compromised dependenciesSchedule regular security reviews and tool updates The goal isn't perfect security — that's impossible. The goal is managed risk, informed decisions, and rapid response capabilities. Conclusion: Trust, But Verify Everything Modern software development operates on trust at an unprecedented scale. Every dependency represents a trust relationship. Every container base image. Every CI/CD tool. Every package registry. The interconnectedness that makes modern development so powerful also makes it vulnerable. Supply chain security isn't just about tools — though tools are essential. It's about changing how we think about dependencies. Instead of blind trust, we need informed trust. Instead of hoping for the best, we need monitoring for the worst. Instead of reactive patching, we need proactive defense. The techniques outlined here — dependency scanning, behavioral analysis, container security, CI/CD integration, continuous monitoring — provide a foundation for managing supply chain risk. But tools evolve. Threats evolve. Your security practices must evolve, too. Most developers unknowingly trust dozens or hundreds of third parties every time they build an application. That trust enables incredible innovation, but it also creates incredible risk. The key is making that trust explicit, measured, and continuously validated. Start where you are. Use what you have. Do what you can. Perfect security doesn't exist, but better security always does. Your supply chain security journey begins with a single scan, a single update, a single question: "Do I really know what this code does?" The answer might surprise you. More importantly, it might protect you. More
A Beginner's Guide to Essential Commands to Fix Container Setup Issues
A Beginner's Guide to Essential Commands to Fix Container Setup Issues
By Akaash Vishal Hazarika
Monolith vs Microservices vs Modulith
Monolith vs Microservices vs Modulith
By RENJITH RAMACHANDRAN
A Practical Guide to Modernizing Data Serving Architectures Using DBSQL
A Practical Guide to Modernizing Data Serving Architectures Using DBSQL
By PRAVEEN DOOTAGANI
Top Takeaways From Devoxx Belgium 2025
Top Takeaways From Devoxx Belgium 2025

In October 2025, I visited Devoxx Belgium, and again it was an awesome event! I learned a lot and received quite a lot of information, which I do not want to withhold from you. In this blog, you can find my takeaways of Devoxx Belgium 2025! Introduction Devoxx Belgium is the largest Java conference in Europe. This year, it was already the 22nd edition. As always, Devoxx is being held in the fantastic theatres of Kinepolis Antwerp. Each year, there is a rush on the tickets. Tickets are released in several batches, so if you could not get a ticket during the first batch, you will get another chance. The first two days of Devoxx are Deep Dive days where you can enjoy more in-depth talks (about 2-3 hours) and hands-on workshops. Days three up and including five are the Conference Days where talks are being held in a time frame of about 30-50 minutes. You receive a lot of information! This edition was a special one for me, because I got the opportunity to speak at Devoxx myself, which has been an awesome experience! I gave a Deep Dive session on Monday, but more on that later. Enough for the introduction, the next paragraphs contain my takeaways from Devoxx. This only scratches the surface of a topic, but it should be enough to make you curious to dive a bit deeper into the topic yourself. Do check out the Devoxx YouTube channel. All the sessions are recorded and can be viewed there. If you intend to view them all, there are 250 of them. Artificial Intelligence Let's start with AI first. More and more AI-related talks are given, which makes Devoxx Belgium also the largest AI conference in the world. But there are sufficient other topics to choose from, but I cannot neglect the importance of AI during this conference. AI Agents Agents are on the rise, and the major libraries for using AI with Java have support for it, or are working on this topic. In general, they all support three flows (explanation is mainly taken from the LangChain4j documentation): Sequential workflow: A sequential workflow is the simplest possible pattern where multiple agents are invoked one after the other, with each agent's output being passed as input to the next agent. This pattern is useful when you have a series of tasks that need to be performed in a specific order.Loop workflow: In this case, you want to improve the output of an LLM in a loop until a certain condition has been met. The agent is invoked multiple times. An end condition can of course also be a maximum number of times in order to prevent the agent to get stuck in the loop.Parallel workflow: With the parallel workflow, you can start multiple agents in parallel and combine their output once they are done with their task. Next to these flows, it is also possible to create agent-to-agent workflows. A2A is an open standard that enables AI agents to communicate and collaborate across different platforms and frameworks, regardless of their underlying technologies. With this approach, you can combine several agents altogether. It is good to know about these capabilities and which support is available in the libraries: LangChain4j, Spring AI, and Agent Development Kit. And do check out the Embabel Framework created by Rod Johnson. This makes use of Goal-Oriented-Action-Planning (GOAP). From LLM orchestration to autonomous agents: Agentic AI patterns with LangChain4j Discover the Agent Development Kit for Java for building AI agents Gen AI Grows Up: Enterprise JVM Agents With Embabel Model Context Protocol If you want to add agents to your AI workflow, you should know about Model Context Protocol (MCP). MCP is a standardized way of interacting with agents. Creating an MCP server is quite easy to do with the above-mentioned libraries. If you want to test your agents, use the MCP Inspector. Something which is not yet addressed sufficiently in the MCP specification is how to secure MCP servers. There is some temporary solution currently, but this probably will change in the near future. Beyond local tools: Deep dive into the Model Context Protocol (MCP) Securing MCP Servers AI Coding Assistants Of course, I have to mention my own Deep Dive. If you want to know more about how to improve the model responses during coding, or if you want to know which tasks can be executed (or not) by AI, etc. Do definitely watch the first part of my Deep Dive. If you are interested in adding MCP servers to your coding workflow so that a model can make use of your terminal, retrieve up-to-date documentation for your libraries, or write End-to-End tests for you, do watch the second part (starting at 1:17). Unlocking AI Coding Assistants: Real-World Use Cases Software Architecture I have read about Architecture Decision Records (ADR) before, and they were mentioned in some talks. But I never had a decent explanation like in the talk I visited. So if you want to get started with ADR, you should definitely take a look at the talk. Creating effective and objective architectural decision records (ADRs) And to continue the architecture paragraph, also watch Making significant Software Architecture decisions. If someone wants to make an architectural decision, you should use the 5 Whys. So, if someone is telling you to use technology A, you ask 'but why?', the person will explain, and then you ask 'but why?', etc. If you still got a decent answer after the fifth why, you are good to go. This and other tips are given in this talk. Security Spring Security I always try to visit a talk about Spring Security, just to freshen up my knowledge and to learn new things, of course. This year, I went to a Spring Security Authorization Deep Dive. You learn about Request, Method, and Object authorization, and how to design your security authorization. Authorization in Spring Security: permissions, roles and beyond Vulnerabilities Ah, vulnerabilities... often a nightmare for developers. Because we need to update our dependencies often. This talk explains CVEs, SBOMs, how to expose your SBOM by means of Spring Boot Actuator, how to use Dependency Track to manage your SBOMs, etc. And also that you should use distroless base images for your own container images in order to reduce the number of dependencies in your container. From Vulnerability to Victory: Mastering the CVE Lifecycle for Java Developers Others Java 25 Between all the AI content, we would almost forget that Java 25 has been released on the 16th of September. In order to get a complete overview, you should take a look at Java 21 to 25 - Better Language, Better APIs, Better Runtime. I was unfortunately not able to attend this Deep Dive because it was scheduled together with my Deep Dive. But that is the beauty of Devoxx Belgium: all talks are recorded and available the next day. This is definitely one of the first talks I will look at. If you are interested in what is coming forward, you should take a look at Weather the Storm: How Value Classes Will Enhance Java Performance. Value classes are immutable and also available for Records. You will get the same performance as with primitive types, meaning that creating value classes comes at almost no performance cost. Spring Boot 4 Another major release coming up is Spring Boot 4 and Spring Framework 7, which is scheduled for November 2025. Discover the new HTTP client, the use of JSpecify annotations, Jackson 3, API versioning, and so on. Bootiful Spring Boot IntelliJ IDEA If you are a Java developer, you probably are using IntelliJ IDEA. IntelliJ covers quite some features and also a lot of them you do not know about. Learn more about it and watch to be more productive with IntelliJ IDEA. You will definitely learn something new. If you are using Spring Boot, you should install the Spring Debugger plugin. At least the ability to see which properties files are loaded, which beans are loaded, is already so valuable that it will help you during debugging. Spring Debugger: Behind The Scenes of Spring Boot Conclusion Devoxx 2025 was great, and I am glad I was able to attend the event. As you can read in this blog, I learned a lot and I need to take a closer look at many topics. At least I do not need to search for inspiration for future blogs!

By Gunter Rotsaert DZone Core CORE
Deployable Architecture: The Cornerstone of Scalable Platform Engineering
Deployable Architecture: The Cornerstone of Scalable Platform Engineering

As architects, you’ve likely seen the same story unfold across growing organizations: teams move fast, each solving problems in their own way — building pipelines, wiring infrastructure, and embedding security into their services from scratch. Initially, it works. But as the organization scales, the cracks begin to show. Environments drift. Deployments become brittle. Governance becomes reactive. And suddenly, the well-intentioned architecture you’ve crafted becomes challenging to replicate, secure, or evolve consistently across teams. This is where platform engineering earns its place — not as a buzzword, but as a strategic discipline that delivers consistency, control, and speed in software delivery. And at the heart of every successful platform initiative lies a foundational element: deployable architecture. Deployable architecture is more than prewritten code or templates — it’s a systematic approach to designing, packaging, and delivering infrastructure and services in a way that is scalable, repeatable, and secure. It allows architects to encode best practices into blueprints that teams can consume confidently — knowing they’re inheriting compliance, observability, performance, and guardrails by design. In this blog, we’ll explore how deployable architecture acts as the cornerstone of scalable platform engineering — empowering architects to go beyond designing systems to enabling organizations to deploy them at scale, with consistency and control. What Is Deployable Architecture? Deployable Architecture can be understood as a set of pre-designed, reusable blueprints for both infrastructure and application deployment. Unlike ad-hoc scripts or one-off configurations, deployable architecture packages battle-tested best practices into production-ready templates. These blueprints are not just about convenience — they provide consistency, reliability, and scalability across teams and environments. By embedding security, compliance, monitoring, and resilience standards, they ensure every deployment inherits the same quality and safeguards, no matter who executes it or where it runs. Key attributes include: Reusability: Standard designs that work across multiple teams and environments.Consistency: Every deployment follows the same high-quality, compliant structure.Automation: Rapid provisioning using Infrastructure as Code (IaC).Governance: Security, compliance, and policy enforcement built in by design. By offering these advantages, deployable architecture transforms platform engineering from a reactive activity into a proactive, scalable discipline. Why It’s the Cornerstone of Scalability Scalability in platform engineering isn’t just about handling more users or bigger workloads—it’s about scaling practices, knowledge, and operations. Deployable Architecture makes this possible: Standardization across teams – Without reusable blueprints, each team reinvents infrastructure and workflows. Deployable Architecture ensures uniformity, reducing fragmentation and operational overhead.Reduced cognitive load – Developers and operators don’t need to worry about setting up security groups, monitoring dashboards, or network policies every time. These elements are already baked into the architecture.Faster time to value – Deployments that once took weeks can be completed in hours, accelerating innovation cycles.Compliance at scale – By embedding organizational policies into reusable templates, every environment deployed is compliant by default — reducing risks and audit headaches. The Link to Platform Engineering Platform engineering thrives on self-service, automation, and consistency. Deployable Architecture is what enables these principles: For developers, it provides the guardrails and golden paths to build applications without second-guessing infrastructure.For operations teams, it ensures predictable, compliant deployments.For the business, it ensures faster delivery while maintaining governance and reliability. Without a deployable architecture, platform engineering risks devolving into siloed practices, with each team solving the same problems repeatedly. Benefits for the Organization Deployable architecture converts institutional best practices into repeatable, auditable, and self-service capabilities — improving speed, safety, and cost simultaneously, with clear KPIs to prove it. Strategic and Speed (Faster time-to-market): Reusable blueprints cut provisioning and setup from weeks to hours. Measure: DORA Lead Time for Change, Deployment Frequency, Time-to-First-Deploy.Reliability and risk (Higher service reliability): Opinionated defaults and tested patterns reduce misconfigurations and change failures. Measure: Change Failure Rate, Incident Rate, MTTR.Security and compliance (Compliance by default): Policy-as-code, immutable baselines, and standardized secrets/KMS make every environment audit-ready. Measure: Policy pass rate, audit findings closed, vulnerability remediation SLA, % signed artifacts/SBOM coverage.Cost and FinOps (Lower TCO via standardization): Reuse > rebuild; enforced tagging, right-sizing, and auto-shutdown reduce waste. Measure: % tagged spend, idle/over-provisioned resources, and unit cost per deploy/service.Governance and Control (Consistent guardrails at scale): Versioned blueprints provide traceability, change history, and easy rollbacks. Measure: Drift incidents, variance across environments, and rollback mean time.Scalability and reuse (Scale practices, not just workloads): One blueprint serves many teams, regions, and clouds with minimal variation. Measure: Blueprint adoption rate, forks vs. deviations, number of teams onboarded. Comparison of Deployable Architecture Terminologies Across Cloud Providers Cloud ProviderTerminology usedfocus area IBM Cloud Deployable Architectures, Secure Landing Zones Compliance-first, automation, industry workloads AWS Solutions Library, Reference Architectures, Landing Zones Enterprise multi-account setup, automation, and governance. Microsoft AzureAzure Verified Modules, CAF (Cloud Adoption Framework) Landing Zones, Deployment Stacks Standardized deployments, policy enforcement, scalability Google Cloud (GCP) Enterprise foundations blueprint, Solutions, Landing Zones IaC-driven deployments, secure foundations, compliance From Blueprints to Capabilities The diagram below outlines the correspondence between layers, from IaC foundations and governance to service integration and golden paths to self-service. Boosting Developer Velocity Through Deployable Architecture Developer velocity increases when cognitive load, hand-offs, and “reinventing the wheel” are eliminated. Deployable architecture makes this possible by transforming infrastructure, security, and runtime conventions into reusable, production-ready blueprints. With these blueprints, teams can spin up environments in minutes — freeing them to focus on building features instead of rebuilding foundations. How It Accelerates Teams One-click environments: Pre-tested IaC modules (networking, IAM, secrets, observability) spin up a compliant stack on demand—no ticket queues.Golden path templates: Opinionated repos/starters wire in CI/CD, test scaffolds, Helm/Kustomize, GitOps rules, SLIs/SLOs, and alerts from day one.Policy-as-code by default: Security and compliance are embedded (OPA/Azure Policy/IBM SCC), eliminating late-stage rework.Batteries-included observability: Dashboards, tracing, logs, and SLO burn-rates ship with the template — issues surface early.Standard contracts: Platform APIs for data, messaging, identity, and storage reduce glue code and integration drift. Enablement Patterns Developer portal integration: Surface blueprints as catalog items with guardrails, docs, and “provision” buttons (Backstage/Port/IBM Cloud Projects).Versioned baselines: Semantic-version your deployables; publish release notes and migration guides.Default ≠ locked-in: Provide safe “escape hatches” with reviewable, policy-checked deviations. Design Principles and Anti-Patterns Building a robust and scalable deployable architecture isn’t just about tools and templates — it’s about following sound engineering principles while actively avoiding design pitfalls. This section outlines the core design principles that underpin effective deployable architectures, along with the anti-patterns that often sabotage them. Key Design Principles Declarative over imperative – Declarative definitions (e.g., Terraform, Helm) describe what the desired state is, not how to achieve it.Composability and modularity – Modular architecture allows reusable components (infra modules, service templates, and CI/CD steps) to be composed for different use cases.Secure-by-default – Security should not be optional or bolted on after deployment.Environment parity – Environments (dev, staging, and prod) should be as similar as possible to reduce "it worked on my machine" issues.Git as the source of truth – Git provides version control, auditability, and rollback capability.Observability built in – Logs, metrics, and traces are critical for debugging and performance. Common Anti-Patterns to Avoid Hardcoding configuration – Use parameterized templates, secret managers, and external config maps.Over-engineering or over-abstraction – Keep it simple. Favor transparency and ease of use over abstract perfection.Mixing runtime logic with deployment logic – Keep deployment logic declarative and parameterized. Separate concerns.Lack of version control for blueprints – Treat blueprints and architecture templates as code with semantic versioning and Git governance.No feedback loop from consumers – Maintain active feedback channels. Iterate on blueprints based on usage data and team input. Conclusion Deployable architecture is no longer a “nice to have” — it is the essential foundation that enables modern platform engineering to deliver at scale, with speed, security, and consistency. From codified blueprints and reusable modules to secure-by-default environments and developer self-service, deployable architectures act as the connective tissue between infrastructure, application, and platform teams. As organizations move toward building platforms as products, deployable architecture becomes the product’s backbone — one that enables innovation at speed without compromising reliability or control. In platform engineering, the true measure of success isn’t just uptime — it’s how quickly and safely your teams can deliver value. Deployable architecture makes that possible.

By Josephine Eskaline Joyce DZone Core CORE
Building Reactive Microservices With Spring WebFlux on Kubernetes
Building Reactive Microservices With Spring WebFlux on Kubernetes

Migrating from a monolithic Java 8 system to a reactive microservice architecture on Kubernetes allowed us to dramatically improve performance and maintainability. In this article, I’ll share our journey, key Spring Cloud Kubernetes features we adopted, the challenges we faced during development, and the lessons we learned along the way. Business Logic We have a data processing logic that streams information into S3 storage using Kafka, Spark Streaming, and Iceberg. Initially, we encountered multiple challenges, such as file optimization issues and Spark’s unpredictable memory behavior. After addressing these issues, we achieved significant cost savings. Once the insert service was completed, we needed to select an appropriate search engine service. We chose Trino as it fulfilled the needs of our data science department. We also serve customers who perform operations on our S3 data, which can result in high system load. Before this modernization, our platform ran on an old monolithic architecture built with Java 8, which created several performance and maintenance challenges. Old Monolith Architecture Our monolithic architecture was an example of a legacy design that lacked scalability and flexibility. It was a mix of MapReduce jobs, insert services connected to various databases, search services, and schema services. When one component experienced issues, the entire system — and therefore our customers — were affected. Small code fixes could take a significant amount of time due to the tightly coupled structure. Additionally, most of the libraries were deprecated, and updating them caused compatibility issues across the stack. We were limited by Java 8’s older garbage collection and incompatibility with modern libraries. We also relied on Zookeeper to store global parameters, which was cumbersome to maintain and added operational overhead. New Reactive Architecture We decided to extract the search service and rebuild it using reactive Spring technology on modern Java, deploying it via Spring Cloud Kubernetes. Spring Cloud Kubernetes provides Spring Cloud interface implementations that consume native Kubernetes services. After exploring its capabilities, we implemented several key features described below. Key Spring Cloud Kubernetes Features A. ConfigMap Property Source Kubernetes provides a resource called ConfigMap to externalize configuration parameters in the form of key-value pairs or embedded application.properties or application.yaml files. The Spring Cloud Kubernetes Config project makes these ConfigMap instances available at application startup and triggers hot reloading of beans or the Spring context when changes are detected. This allows dynamic configuration without restarts. Example configuration: YAML spring: application: name: cloud-k8s-app cloud: kubernetes: config: name: default-name namespace: default-namespace sources: - name: c1 - namespace: n2 - namespace: n3 name: c3 B. Secrets Property Source Kubernetes also supports Secrets for storing sensitive information such as passwords, OAuth tokens, and API keys. We integrated these using Spring Cloud Kubernetes Secrets configuration. Example configuration: YAML cloud: kubernetes: secrets: enabled: true fail-fast: true sources: - name: Secret namespaces: hades-h3 C. Leader Election Spring Cloud Kubernetes includes a leader election mechanism that implements Spring Integration’s leader election API using a Kubernetes ConfigMap. The following configuration enables leader election. YAML leader: enabled: true When a pod becomes the leader, the following code executes: @EventListener() public void foo(OnGrantedEvent event) { if (event.getContext() != null) { logger.info(new LogEvent("I am foo leader.")); executeCreateView(); } else { logger.info(new LogEvent("Skipping foo because I'm not the leader.")); } } The method executeCreateView() creates a view every 30 seconds in our Trino search engine. We implemented the leader mechanism because only one instance should perform this operation, preventing race conditions among multiple pods. D. Spring Cloud Kubernetes Configuration Watcher Kubernetes allows mounting ConfigMaps or Secrets as volumes inside containers. When their contents change, the mounted volume updates automatically. We developed a GlobalConfig class responsible for reading configuration values from the Kubernetes ConfigMap. We used the @RefreshScope annotation so that any change in configuration automatically triggers a bean reload, allowing us to apply updates without restarting the application. E. Spring Boot Actuator Spring Boot Actuator provides production-ready features for monitoring, metrics collection, and health checks without requiring custom implementation. We integrated it to monitor application performance, analyze traffic, and track database states. We also used these metrics to build Grafana dashboards for better visibility. Some of the metrics we collect with the help of the actuator Problems During Development During testing, we observed an issue where CPU usage increased gradually over time. After a detailed investigation, we discovered that the Actuator was collecting metrics for every external IP calling the service, which caused a memory and CPU increase. We resolved this by using MeterFilter.deny() to filter out unnecessary metrics and retain only what we needed. We also faced the challenge of designing a single class that could read configuration data from both the Kubernetes ConfigMap and local application resources. We implemented it as follows: YAML @ConfigurationProperties(prefix = "global") @RefreshScope @Configuration public class GlobalConfig extends GitLabPush { private String config; public void setConfig(String config) { this.config = config; } } The prefix “global” defines the upper level of the ConfigMap. When scanning, the field “config” is retrieved from the map with its associated values. Example ConfigMap entry: Shell global.config: {"field": "data"} Another challenge arose when updating the Kubernetes ConfigMap via our API. We needed to synchronize these changes with our Argo projects, where YAMLs were stored. To ensure consistency, we added a pushToGit() function that used the GitLab API to automatically commit updates to the Git repository. Lessons Learned Refactoring our monolithic service into microservices provided a far more efficient development process, improved performance, and granted access to the full range of features in Spring Cloud Kubernetes. We gained scalability, dynamic configuration management, and simplified observability. This migration allowed our teams to deploy updates faster, recover from issues quickly, and reduce maintenance overhead. Conclusion Transitioning from a monolithic Java 8 architecture to a reactive microservice environment built with Spring WebFlux and Kubernetes transformed the reliability and agility of our system. The combination of ConfigMaps, Secrets, Leader Election, and Actuator monitoring enabled us to create a flexible, cloud-native platform ready to handle complex workloads at scale.

By Mikhail Povolotskii
A Comprehensive Analysis of Async Communication in Microservice Architecture
A Comprehensive Analysis of Async Communication in Microservice Architecture

Microservice architecture has become a standard practice for companies, small and large. One of the challenges is communication between different services. I’ve worked with microservices for a decade now, and I’ve seen a lot of people struggle to understand how to implement a proper communication protocol. In this series of articles, I’ll share my knowledge and expertise on async communication in microservices. Part I: Introduction To Asynchronous Communication In MicroservicesPart II: Cloud Message Brokers – AWS SQS/SNS vs Google Pub/SubPart III: Messaging Patterns and Best Practices in Asynchronous SystemsPart IV: Implementation Considerations for Production SystemsPart V: Advanced Topics and Migration Strategies Introduction To Asynchronous Communication In Microservices The Evolution from Monoliths to Microservices The software industry has witnessed a dramatic shift from monolithic architectures to distributed microservices over the past decade. This transformation has brought unprecedented scalability and flexibility, but it has also introduced new challenges in how services communicate with each other. At the heart of this challenge lies a fundamental question: how do independent services exchange information reliably, efficiently, and without creating tight coupling that defeats the purpose of microservices in the first place? Asynchronous communication has emerged as the answer to this question, becoming the backbone of modern distributed systems. Unlike traditional synchronous request-response patterns, where services wait for immediate replies, asynchronous communication allows services to send messages and continue processing without blocking. This seemingly simple shift in approach has profound implications for system design, scalability, and resilience. Understanding Synchronous vs. Asynchronous Communication In synchronous communication, exemplified by REST APIs and gRPC calls, Service A makes a request to Service B and waits for a response before continuing. This pattern is intuitive and mirrors how we often think about program execution. However, it creates direct dependencies between services. If Service B is slow, unavailable, or experiencing high load, Service A suffers the consequences directly. This tight coupling cascades through the system, where the failure or slowness of one service can bring down entire chains of dependent services. Asynchronous communication breaks this direct dependency. When Service A needs to inform Service B about an event or request an action, it publishes a message to an intermediary system called a message broker. Service A doesn't wait for Service B to process the message; it immediately continues with its own work. Service B, whenever it's ready and available, consumes the message from the broker and processes it. This decoupling is the cornerstone of resilient, scalable microservices architectures. The Role of Message Brokers Message brokers are the infrastructure components that make asynchronous communication possible. They act as intermediaries that receive messages from publishers (services sending messages) and deliver them to consumers (services receiving messages). Modern cloud platforms provide managed message broker services that handle the complexity of message storage, delivery, retry logic, and scaling. The two dominant cloud message broker platforms are Amazon Web Services (AWS) with its SQS (Simple Queue Service) and SNS (Simple Notification Service), and Google Cloud Platform (GCP) with its Pub/Sub service. These platforms have democratized access to enterprise-grade messaging infrastructure, allowing teams of any size to build sophisticated distributed systems without managing the underlying infrastructure. AWS SQS provides a queue-based model where messages are stored in a queue and consumed by one or more workers. SNS implements a publish-subscribe pattern where messages are broadcast to multiple subscribers. Google Pub/Sub combines both patterns into a unified service that supports both queue-like subscriptions and fan-out patterns. All these services guarantee message durability, at least once delivery, and can scale to handle millions of messages per second. Why Asynchronous Communication Matters The importance of asynchronous communication in microservices cannot be overstated. It addresses several critical architectural concerns: First, it enables true service independence. Services can evolve, scale, and deploy independently without coordinating with their dependencies. A service can publish events without knowing or caring how many other services consume them, or what those services do with the information. This decoupling is critical to the stability of a distributed system. Second, it improves system resilience. When services communicate asynchronously, the temporary unavailability of a consumer doesn't impact the publisher. Messages accumulate in the broker and are processed when the consumer recovers. This natural buffering effect absorbs traffic spikes and service failures without cascading problems through the system. Third, it facilitates better scalability. Asynchronous systems can handle variable load more gracefully. If messages arrive faster than they can be processed, they queue up in the broker, and consumers can scale horizontally to handle the backlog. This is far more difficult with synchronous communication, where callers must wait for responses, limiting throughput and creating pressure on upstream services. Fourth, it enables event-driven architectures. Rather than services actively polling for changes or tightly coordinating their actions, they can react to events published by other services. This creates more loosely coupled, maintainable systems where adding new functionality often means creating a new consumer for existing events rather than modifying existing services. Real-World Frameworks: Hedwig and Taskhawk The practical implementation of asynchronous communication patterns has been simplified by frameworks I’ve built, such as Hedwig and Taskhawk, both designed to work seamlessly with AWS and GCP message brokers. Hedwig is an inter-service communication bus that focuses on message validation and enforcing contracts between publishers and consumers. It validates message payloads before they're sent, catching incompatibilities early in development rather than in production. Hedwig maintains separation of concerns between consumers and publishers, ensuring services remain loosely coupled while the message schema provides a clear contract. This approach makes asynchronous communication as reliable and predictable as synchronous APIs while maintaining all the benefits of decoupling. Taskhawk takes a different approach, focusing on asynchronous task execution similar to Celery but designed specifically for cloud message brokers. Any function can be converted into a Taskhawk task, making it simple to offload work to background workers. Tasks can be scheduled using native cloud services like AWS CloudWatch Events or GCP Cloud Scheduler, enabling sophisticated workflow patterns without additional infrastructure. Both frameworks abstract away the complexity of working directly with SQS, SNS, or Pub/Sub, providing developer-friendly APIs while leveraging the reliability and scalability of cloud infrastructure. They represent the maturity of asynchronous messaging patterns, making them accessible to teams without deep expertise in distributed systems. The Journey Ahead This article has introduced the fundamental concepts of asynchronous communication in microservices and explained why it has become essential for modern distributed systems. The remaining articles in this series will dive deeper into specific aspects: Article 2 will explore message broker architectures in detail, comparing AWS SQS/SNS with Google Pub/Sub, examining their features, guarantees, and appropriate use cases. Article 3 will cover messaging patterns and best practices, including event sourcing, CQRS, saga patterns, and handling failures and implementing idempotency. Article 4 will focus on implementation considerations, including message schema design, versioning strategies, monitoring and observability, and testing asynchronous systems. Article 5 will address advanced topics and challenges, including ordering guarantees, exactly-once semantics, handling poison messages, and migrating from synchronous to asynchronous architectures. Understanding asynchronous communication is no longer optional for building scalable, resilient microservices. It's a fundamental skill that every team working with distributed systems must master. The shift from synchronous to asynchronous thinking represents not just a technical change, but a conceptual evolution in how we design and reason about software systems.

By Aniruddha Maru
Kubernetes Debugging Recipe: Practical Steps to Diagnose Pods Like a Pro
Kubernetes Debugging Recipe: Practical Steps to Diagnose Pods Like a Pro

Automation isn’t optional at enterprise scale. It’s resilient by design. Kubernetes provides remarkable scalability and resilience , but when pods crash, even seasoned engineers struggle to translate complex and cryptic logs and events. This guide walks you through the spectrum of AI-powered root cause analysis and manual debugging, combining command-line reproducibility and predictive observability approaches. Introduction Debugging distributed systems is an exercise in controlled chaos. Kubernetes abstracts away deployment complexity, but those same abstractions can hide where things go wrong. The goal of this article is to provide a methodical, data-driven approach to debugging and then extend that process with AI and ML for proactive prevention. We’ll cover: Systematic triage of pod and node issues.Integrating ephemeral and sidecar debugging.Using ML models for anomaly detection.Applying AI-assisted Root Cause Analysis (RCA).Designing predictive autoscaling and compliance-safe observability. Step-by-Step Implementation Step 1: Inspect Pods and Events Start by collecting structured evidence before introducing automation or AI. Key commands: Shell kubectl describe pod <pod-name> kubectl logs <pod-name> -c <container> kubectl get events --sort-by=.metadata.creationTimestamp Interpretation checklist: Verify container state transitions (Waiting, Running, and Terminated).Identify patterns in event timestamps correlated with restarts, which often signal resource exhaustion.Capture ExitCode and Reason fields.Collect restart counts: Shell kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].restartCount}' AI extension: Feed logs and event summaries into an AI model (like GPT-4 or Claude) to quickly surface root causes: “Summarize likely reasons for this CrashLoopBackOff and list next diagnostic steps.” This step shifts engineers from reactive log hunting to structured RCA. Step 2: Ephemeral Containers for Live Diagnosis Ephemeral containers are your “on-the-fly” debugging environment. They let you troubleshoot without modifying the base image, which is essential in production environments. Command: Shell kubectl debug -it <pod-name> --image=busybox --target=<container> Inside the ephemeral shell: Check environment variables: env | sortInspect mounts: df -h && mount | grep appTest DNS: cat /etc/resolv.conf && nslookup google.comVerify networking: curl -I http://<service-name>:<port> AI tip: Feed ephemeral-session logs to an AI summarizer to auto-document steps for your incident management system, creating reusable knowledge. Step 3: Attach a Debug Sidecar (For Persistent Debugging) In environments without ephemeral containers (e.g., OpenShift or older clusters), add a sidecar container. Example YAML: YAML containers: - name: debug-sidecar image: nicolaka/netshoot command: ["sleep", "infinity"] Use cases: Network packet capture with tcpdump.DNS and latency verification with dig and curl.Continuous observability in CI environments. Enterprise note: At a large tech company, scale clusters, debugging sidecars are often deployed only in non-production namespaces for compliance. Step 4: Node-Level Diagnosis Pods inherit instability from their hosting nodes. Commands: Shell kubectl get nodes -o wide kubectl describe node <node-name> journalctl -u kubelet --no-pager -n 200 sudo crictl ps sudo crictl logs <container-id> Investigate: ResourcePressure (MemoryPressure, DiskPressure).Kernel throttling or CNI daemonset failures.Container runtime errors (containerd/CRI-O). AI layer: ML-based observability (e.g., Dynatrace Davis or Datadog Watchdog) can automatically detect anomalies such as periodic I/O latency spikes and recommend affected pods. Step 5: Storage and Volume Analysis Persistent Volume Claims (PVCs) can silently cause pod hangs. Diagnostic workflow: Check mounts: Shell kubectl describe pod <pod-name> | grep -i mount Inspect PVC binding: Shell kubectl get pvc <pvc-name> -o yaml Validate StorageClass and node access mode (RWO, RWX).Review node dmesg logs for mount failures. AI insight: Anomaly detection models can isolate repeating I/O timeout errors across nodes- clustering them to detect storage subsystem degradation early. Step 6: Resource Utilization and Automation Resource throttling leads to cascading restarts. Monitoring commands: Shell kubectl top pods kubectl top nodes Optimization: Fine-tune CPU and memory requests/limits.Use kubectl get hpa to confirm scaling thresholds.Implement custom metrics for queue depth or latency. HPA example: YAML apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-service-hpa spec: minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 Automation isn’t optional at enterprise scale. It’s resilient by design. Step 7: AI Augmented Debugging Pipelines AI is transforming DevOps from reactive incident response to proactive insight generation. Applications: Anomaly detection: Identify outlier metrics in telemetry streams.AI log summarization: Extract high-value signals from terabytes of text.Predictive scaling: Use regression models to forecast utilization.AI-assisted RCA: Rank potential causes with confidence scores. Example AI call: Shell cat logs.txt | openai api chat.completions.create \ -m gpt-4o-mini \ -g '{"role":"user","content":"Summarize probable root cause"}' These techniques minimize mean time to recovery (MTTR) and mean time to detection (MTTD). Step 8: AI-Powered Root Cause Analysis (RCA) Traditional RCA requires manual correlation across metrics and logs. AI streamlines this process. Approach: Cluster error signatures using unsupervised learning.Apply attention models to correlate metrics (CPU, latency, I/O).Rank potential causes with Bayesian confidence.Auto-generate timeline summaries for postmortems. Example workflow: Collect telemetry and store in Elastic AIOps.Run ML job to detect anomaly clusters.Feed summary to LLM to describe likely failure flow.Export insight to Jira or ServiceNow. This hybrid system merges deterministic data with probabilistic reasoning, ideal for financial or mission-critical clusters. Step 9: Predictive Autoscaling Reactive scaling waits for metrics to breach thresholds; predictive scaling acts before saturation. Implementation path: Gather historic CPU, memory, and request metrics.Train a regression model to forecast 15-minute utilization windows.Integrate predictions with Kubernetes HPA or KEDA.Validate performance using synthetic benchmarks. Example (conceptual): Python # pseudo-code for predictive HPA predicted_load = model.predict(metrics.last_30min()) if predicted_load > 0.75: scale_replicas(current + 2) At a large tech company, class clusters, predictive autoscaling can reduce latency incidents by 25–30%. Step 10: Compliance and Security in AI Debugging AI-driven pipelines must respect governance boundaries. Guidelines: Redact credentials and secrets before log ingestion.Use anonymization middleware for PII or transaction IDs.Apply least privilege RBAC for AI analysis components.Ensure model storage complies with data residency regulations. Security isn’t just about access - it’s about maintaining explainability in AI-assisted systems. Step 11: Common Failure Scenarios categorysymptomroot causefixRBACForbiddenMissing role permissionsAdd RoleBindingImageImagePullBackOffWrong registry secretUpdate and re-pullDNSTimeoutStale CoreDNS cacheRestart CoreDNSStorageVolumeMount failPVC unboundRebind PVCCrashRestart loopInvalid env varsCorrect configuration AI correlation engines now automate this table in real time, linking symptoms to resolution recommendations. Step 12: Real World Enterprise Example Scenario: A financial transaction service repeatedly fails post-deployment. Process: Logs reveal TLS handshake errors.AI summarizer highlights expired intermediate certificate.Jenkins assistant suggests reissuing the secret via cert-manager.Deployment revalidated successfully. Result: Incident time reduced from 90 minutes to 8 minutes - measurable ROI. Step 13: The Future of Autonomous DevOps The next wave of DevOps will be autonomous clusters capable of diagnosing and healing themselves. Emerging trends: Self-healing deployments using reinforcement learning.LLM-based ChatOps interfaces for RCA.Real-time anomaly explanation using SHAP and LIME interpretability.AI governance models ensuring ethical automation. Vision: The DevOps pipeline of the future isn’t just automated, it’s intelligent, explainable, and predictive. Conclusion Debugging Kubernetes efficiently is no longer about quick fixes, and it’s about building feedback systems that learn. Modern debugging workflow: InspectDiagnoseAutomateApply AI RCAPredict When humans and AI collaborate, DevOps shifts from firefighting to foresight.

By Chandrasekhar Rao Katru
From Model to Microservice: A Practical Guide to Deploying ML Models as APIs
From Model to Microservice: A Practical Guide to Deploying ML Models as APIs

You’ve done it. You’ve spent weeks cleaning data, feature engineering, and hyperparameter tuning. You have a Jupyter Notebook showing a beautiful .fit() and a .predict() that works perfectly. The model accuracy is 99%. Victory! But now comes the hard part. Your stakeholder asks, "That's great, but how do we get this into the new mobile app?" Suddenly, the reality hits: a model in a notebook delivers zero business value. To be truly useful, your machine learning model needs to be integrated into applications, and the most robust, scalable way to do so is to deploy it as a Microservice API. In this article, we'll bridge the gap between data science and software engineering. We'll walk through a practical, production-ready pattern for wrapping your ML model in a REST API, containerizing it with Docker, and understanding the principles that enable it to run at scale. The "Why": Beyond the Notebook Deploying an ML model as an API isn't just a technical step; it's a philosophical one. It shifts your model from a static artifact to a dynamic, callable service. This approach offers key advantages: Loose coupling: Your front-end, mobile app, or other backend services don't need to know the intricacies of your model (e.g., TensorFlow vs. Scikit-learn). They just need to know the API endpoint and the expected JSON payload.Technology agnosticism: The team building the consumer application can use any programming language (JavaScript, Go, Java) as long as it can send an HTTP request.Scalability: You can scale your model independently of the rest of your application architecture using a container orchestrator like Kubernetes.Simplified MLOps: This pattern is the foundation for continuous integration and deployment (CI/CD) for machine learning, making model updates safe and repeatable. The "How": A Practical Python Example With FastAPI Let's build a simple prediction service for a hypothetical model that classifies text sentiment. We'll use FastAPI for its high performance, automatic interactive documentation, and Python-native feel. Step 1: The Model Wrapper First, we create a prediction.py file that encapsulates our model logic. For this example, we'll use a placeholder, but this is where you would load your serialized model.pkl or model.h5 file. Python # prediction.py class SentimentModel: def __init__(self): # In reality, you would load your pre-trained model here # self.model = joblib.load('model.pkl') pass def predict(self, text: str) -> dict: # This is a mock prediction for demonstration # Replace this with actual model inference mock_sentiment = "positive" if "good" in text.lower() else "negative" confidence = 0.95 return {"sentiment": mock_sentiment, "confidence": confidence} Step 2: The API Layer With FastAPI Next, we create our main application file, main.py. This is where we define our web server and endpoints. Python # main.py from typing import Dict from fastapi import FastAPI from pydantic import BaseModel from prediction import SentimentModel # Define the data model for the request body using Pydantic # This provides automatic validation and documentation class PredictionRequest(BaseModel): text: str # Initialize the FastAPI app and our model app = FastAPI(title="Sentiment Analysis API", version="1.0") model = SentimentModel() @app.get("/") def read_root(): return {"message": "Sentiment Analysis API is Live!"} @app.post("/predict", response_model=Dict[str, str]) def predict(request: PredictionRequest): # The core of the service: call the model's predict method prediction_result = model.predict(request.text) return prediction_result # For local development if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) Key Features of this Code Pydantic models: The PredictionRequest class automatically validates that the incoming request has a text field that is a string.Type hints: Extensive use of type hints improves code clarity and enables better IDE support.Automatic docs: Run this app and go to http://localhost:8000/docs to see interactive Swagger UI documentation generated automatically. This is a huge win for API consumers. Step 3: Containerization With Docker To ensure our service runs consistently anywhere, we package it into a Docker container. Create a Dockerfile: Dockerfile # Use an official Python runtime as a base image FROM python:3.9-slim # Set the working directory in the container WORKDIR /app # Copy the current directory contents into the container COPY requirements.txt . COPY . . # Install any needed dependencies specified in requirements.txt RUN pip install --no-cache-dir -r requirements.txt # Make port 8000 available to the world outside this container EXPOSE 8000 # Run the application CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] And a requirements.txt file: Plain Text fastapi[all] uvicorn # scikit-learn # tensorflow # pandas Now, you can build and run your microservice: Shell docker build -t sentiment-api . docker run -d -p 8000:8000 sentiment-api Your ML model is now a live, scalable web service accessible at http://localhost:8000. Next Steps and Best Practices This is a minimal viable product. For a production system, consider these crucial enhancements: Health checks: Add a /health endpoint for your orchestrator (like Kubernetes) to check the service's liveness and readiness.Configuration management: Use environment variables for configuration (e.g., model file paths, log levels).Logging and monitoring: Implement structured logging and integrate with monitoring tools like Prometheus to track latency, error rates, and prediction distributions.Security: Add authentication (e.g., API Keys, OAuth2) and consider rate limiting to protect your service from abuse.Versioning: Version your API endpoints (e.g., /v1/predict) from the start to manage future model updates without breaking existing clients. Conclusion By wrapping your machine learning model in a well-defined API and containerizing it, you transform it from a scientific experiment into a true engineering component. This "Model-as-a-Microservice" pattern is the cornerstone of modern MLOps, enabling agility, scalability, and robust integration with the rest of your software ecosystem. Stop letting your models gather dust in notebooks. Start deploying.

By Naga Sai Mrunal Vuppala
From Distributed Monolith to Composable Architecture on AWS
From Distributed Monolith to Composable Architecture on AWS

You adopted microservices for independence and agility. Instead, every deployment requires coordinating multiple teams and testing the entire system. What you built is a distributed monolith, complexity spread across systems, but still bound by monolithic coupling. The shift from technical boundaries to business-driven boundaries is the only path to true agility. Many organizations discover too late that microservices alone do not guarantee independence. Domain-Driven Composable Architecture (DDCA) provides a methodology to escape this rigidity. This article is a practical playbook for decomposing services into Packaged Business Capabilities (PBCs) aligned with business domains and mapped to AWS patterns such as EventBridge, Step Functions, and DynamoDB Streams. It explains when DDCA fits and when it does not, and covers security, anti-patterns, and operational realities, so you can adopt composability with a clear view of the investment required. Problem: Technical Boundaries Create Distributed Monoliths Microservices promised independent deployment and team autonomy. Most enterprises failed to achieve these benefits. The reason is that boundaries were drawn around technical layers or organizational convenience, such as REST APIs, database tables, or team size, instead of business domains. This reflects a fundamental misunderstanding of decomposition. Some teams are split by technical concerns, such as a database service, an API gateway service, and a business logic service. This recreates a distributed three-tier architecture where every business change touches multiple services. Others avoid that mistake but swing too far the other way, grouping entire domains like “Rewards” into one giant service. Both approaches create coupling, one through technical layering and the other through overly broad business boundaries. In credit card rewards processing, for instance, an architecture team might create a single RewardsService handling merchant categorization, earning rate calculations, promotional bonuses, milestone tracking, and points ledger, all bundled together because they are considered “rewards logic.” When Merchant Category Codes (MCC) update quarterly, the entire service redeploys with full regression tests across unrelated modules. The result is a distributed infrastructure with load balancers, deployments, and cloud clusters combined with monolithic coupling such as shared databases, coordinated releases, and cascading failures. The outcome is inheriting the complexity of distributed systems without gaining genuine independence. Figure 1: Traditional distributed monolith – rewards service The Solution: Business-Driven Boundaries Domain-Driven Composable Architecture (DDCA) applies Domain-Driven Design principles to define service boundaries by business capability. Bounded contexts define clear business domains where Rewards Processing is fundamentally different from Transaction Authorization. These boundaries become Packaged Business Capabilities (PBCs), independently deployable autonomous units representing discrete business functions that enable true service independence. Figure 2: Domain-driven composable architecture – rewards service The transformation from Figure 1 to Figure 2 represents the shift from technical coupling to business independence. In the monolithic architecture, a single deployment unit forces all five capabilities to scale, deploy, and fail together. In the PBC architecture, each capability runs as its own Lambda function with a dedicated DynamoDB table, and Step Functions replaces the load balancer as the orchestration layer, invoking only the capabilities needed for each card product. Workflow Composition The Rewards Orchestrator (Step Functions in Figure 2) composes workflows from independent PBCs. Premium cards invoke all five PBCs, such as Merchant Classification, Earning Rate Engine, Promotional Multiplier, Spending Milestone Tracker, and Points Ledger. Basic cashback cards invoke only Merchant Classification and Points Ledger with a flat rate. This composable design allows new products and campaigns to be introduced rapidly. For example, a partner card with distinct earning rules can be assembled from existing capabilities in weeks instead of months. Similarly, a new promotional campaign in a distributed monolith would require coordinating multiple teams and lengthy deployments, while in the DDCA approach, developers update only the Promotional Multiplier PBC, deploy that single capability in hours, and launch on time. PBC Design in Practice A PBC adheres to three non-negotiable principles. Data ownership: The PBC owns its data completely. No cross-PBC direct database access. All exchange happens through interfaces or events.Clear interface: A PBC can be invoked synchronously through an API or directly as a Lambda function inside a workflow. It may also publish domain events for asynchronous consumers.Single team ownership: One autonomous team owns deployment, testing, and scaling end to end. For example, the Merchant Classification PBC in Figure 2 owns its DynamoDB table of category mappings. It can be called through a request (such as POST /classify-merchant) or orchestrated directly by Step Functions, and it publishes MerchantCategorized events. The Earning Rate Engine PBC follows the same principles with different rate tables and business rules. When to Commit to DDCA DDCA is a powerful but resource-intensive approach. It is most appropriate for systems with complex business domains and a need for long-term agility. Use DDCA when the core business has complex logic (for example, banking or insurance), you are driving large-scale enterprise modernization, or when multiple independent teams are aligned to business capabilitiesAvoid DDCA when the application is a simple CRUD system, the challenge is mostly technical rather than business complexity, or the team is small and lacks expertise in domain-driven design. Operational Reality Adopting DDCA requires significant operational maturity. Moving from a single RewardsService to multiple independently deployable PBCs increases the number of components to manage, which raises the need for automation, disciplined testing, and robust observability. Expect higher investment in monitoring and infrastructure than with a monolith. Cloud-native tools like Amazon CloudWatch and AWS X-Ray, as well as third-party platforms such as Datadog, New Relic, and Splunk, are essential for visibility across distributed components. Automated pipelines, infrastructure as code, and regression testing frameworks further help maintain consistency at scale. For smaller teams with fewer than fifteen engineers, a well-structured modular monolith is often the more practical starting point. AWS Implementation: Mapping DDCA to AWS Services The table below shows how DDCA principles translate into AWS building blocks. DDCA conceptAWS servicesTechnical PatternBusiness Value Autonomous PBCs Lambda, ECS Fargate Each business capability is deployed independently Deploy Merchant Classification without touching Promotional Multiplier Decoupled Communication EventBridge Event Driven Architecture (Publish/subscribe via events) Promotional failure does not block base rewards processing Resilient Orchestration Step Functions Error handling with fallbacks; Saga Pattern with compensation Rate lookup failure uses base rate, transaction continues Data Consistency DynamoDB, DynamoDB Streams, Lambda, EventBridge Transactional Outbox (write business row and outbox, publish via streams; at least once) Ledger write and outbox persist atomically; publish retried via streams (at least once) Security Isolation IAM, optional VPC or account per context Least privilege per PBC capability (Zero Trust) Basic card orchestrator cannot invoke premium PBCs The AWS Playbook: Three Patterns for Composable Architecture DDCA relies on a few core patterns that reduce coupling and maintain consistency across Packaged Business Capabilities. Pattern 1: Event Driven Communication (Decoupled PBCs) In a monolith, one component failing can bring down the whole system. Event-driven architecture decouples PBCs, so if a downstream capability is unavailable, upstream PBCs continue processing and publish events for later consumption. This avoids cascading failures and maintains partial availability. PBCs publish domain events via Amazon EventBridge instead of synchronous service calls. EventBridge provides schema management, event filtering, and cross-account delivery, making it better suited for composability at scale. SNS and SQS work well for point-to-point messaging, but EventBridge avoids tight coupling. JavaScript await eventBridge.putEvents({ Entries: [{ Source: 'rewards.merchant-classification', DetailType: 'MerchantCategorized', Detail: JSON.stringify({ transactionId: 'txn-12345', merchant: 'Sample Merchant', category: 'retail', confidence: 0.95 }) }] }); If the promotional multiplier service is down, base rewards are still credited immediately. Promotional bonuses apply when the service recovers. Pattern 2: Resilient Orchestration With Step Functions Distributed transactions across PBCs are challenging to implement reliably. AWS Step Functions coordinate multiple PBC workflows with explicit error handling. When a step fails, the workflow applies fallbacks to maintain availability and prevent cascading failures. For example, if the earning rate lookup fails, the workflow applies a base rate instead of failing the transaction. For scenarios that require reversing completed steps, Step Functions can also implement the saga pattern with compensating actions. JSON { "StartAt": "ClassifyMerchant", "States": { "ClassifyMerchant": { "Type": "Task", "Resource": "arn:aws:lambda:merchant-classification-pbc", "Catch": [ { "ErrorEquals": ["ClassificationFailed"], "Next": "UseDefaultCategory" } ], "Next": "CalculateEarningRate" }, "UseDefaultCategory": { "Type": "Pass", "Next": "CalculateEarningRate" }, "CalculateEarningRate": { "Type": "Task", "Resource": "arn:aws:lambda:earning-rate-engine-pbc", "Catch": [ { "ErrorEquals": ["RateLookupFailed"], "Next": "ApplyBaseRate" } ], "Next": "CreditPoints" }, "ApplyBaseRate": { "Type": "Pass", "Next": "CreditPoints" }, "CreditPoints": { "Type": "Task", "Resource": "arn:aws:lambda:points-ledger-pbc", "End": true } } } Pattern 3: Transactional Outbox (Data Consistency) Without atomic guarantees, a PBC may update its database but fail to publish an event, leaving other PBCs with stale data. The transactional outbox pattern solves this by using DynamoDB TransactWriteItems to write both the business change and an outbox record in one atomic operation. A Lambda function reads the outbox via DynamoDB Streams and publishes to EventBridge. This provides at least one delivery without distributed transactions. Consuming PBCs must handle duplicate events since the same event may be delivered multiple times. Security: Zero Trust at PBC Boundaries Security is enforced at the capability level using a zero-trust model that verifies every call and grants least privilege access. Each PBC exposes only what is needed, and callers are authorized by identity and policy rather than network location. This prevents compromise from cascading across capabilities and reduces blast radius. Verify explicitly: Every invocation is authenticated and authorized; IAM is evaluated on each call to the PBC.Least privilege: Service roles allow only specific actions on specific PBCs (for example, lambda:InvokeFunction on Merchant Classification and Points Ledger only).Containment: Each PBC is isolated; optional VPC or account separation strengthens boundaries without changing the interface. JSON { "Statement": [{ "Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": [ "arn:aws:lambda:merchant-classification-pbc", "arn:aws:lambda:points-ledger-pbc" ] }] } This policy is attached to the basic card orchestrator’s execution role, allowing it to invoke only Merchant Classification and Points Ledger, not Earning Rate Engine or Promotional Multiplier. If the orchestrator is compromised, the attacker cannot access premium card capabilities. Avoid the Trap: Three DDCA Anti-Patterns Even with solid intentions, teams often stumble into patterns that undo the benefits of DDCA. These pitfalls recreate the very distributed monolith you are trying to escape. Shared database between PBCs: The most damaging anti-pattern. For example, if Merchant Classification and Earning Rate Engine both query the same rewards database table, you have tight coupling at the data layer. Each PBC must own its data completely.Orchestrator with business logic: The orchestrator should only coordinate. If the Rewards Orchestrator starts deciding which promotions apply or calculating bonus thresholds, business logic has leaked outside the PBC. That logic must stay inside Promotional Multiplier or Milestone Tracker PBCs.Over fragmentation: Splitting capabilities too finely increases network chatter and operational overhead without adding real business value. For example, creating separate PBCs for Calculate Points and Validate Points, even though they always run together and share the same business rules, introduces unnecessary calls and complexity. Both responsibilities should remain within a single Points Engine PBC. Beyond Code: The Organizational and Business Impact The Domain-Driven Design approach aligns technical architecture with business structure, creating a powerful organizational advantage. Team structure: Teams align to business-bound contexts, such as the Rewards Processing Squad. This alignment creates autonomy, and by Conway’s Law, the resulting architecture naturally evolves into a decoupled, higher-quality system.Product agility: Product Owners can compose new card products or reward flows directly from existing Packaged Business Capabilities, without waiting on multiple teams. This accelerates time to market for new offerings.Policy and auditing: Business policy changes, such as updates to rewards eligibility or promotional rules, are isolated to the specific PBC affected. This reduces audit scope and deployment risk. Strangler Fig Migration From Monolith to PBCs Start with your highest value PBC. In rewards processing, Merchant Classification is a strong candidate because it changes frequently with MCC updates, has clear inputs and outputs, and delivers quick wins when deployed independently. Build this PBC alongside the monolith, shift a small percentage of traffic to it, and gradually ramp up over four to six weeks while monitoring accuracy. Retire the legacy code only after full confidence is established. From there, the pace of extraction depends on the value and complexity of each capability. Take one PBC at a time, starting with the most impactful, and move at a pace that fits the system and team capacity. Each extraction becomes easier as teams gain domain knowledge and refine automation. Success can be measured through concrete signals such as faster deployment frequency, shorter regression cycles, lower change failure rates, and reduced lead time for new features. Tracking these signals makes it clear whether the architecture is gaining genuine composability or only shifting complexity to new boundaries. Conclusion Packaged Business Capabilities transform distributed monoliths into genuinely composable architectures. By aligning technical boundaries with business capabilities and mapping them to AWS services such as EventBridge, Step Functions, and DynamoDB Streams, teams can reduce coupling and accelerate delivery. The payoff is measurable as deployment cycles shorten from weeks to days, audit scope narrows to individual modules, and new products can be assembled by composing existing capabilities instead of rewriting code. Composability is not about making services small; it is about making them interchangeable. With intentional boundaries and disciplined execution, organizations can move beyond accidental coupling and achieve lasting agility.

By Elakkiya Daivam
Strategic Domain-Driven Design: The Forgotten Foundation of Great Software
Strategic Domain-Driven Design: The Forgotten Foundation of Great Software

When teams talk about domain-driven design (DDD), the conversation often jumps straight to code — entities, value objects, and aggregates. Yet, this is where most projects begin to lose direction. The essence of DDD is not in the tactical implementation, but in its strategic foundation — the part that defines why and where we apply the patterns in the first place. The strategic aspect of DDD is often overlooked because many people do not recognize its importance. This is a significant mistake when applying DDD. Strategic design provides context for the model, establishes clear boundaries, and fosters a shared understanding between business and technology. Without this foundation, developers may focus on modeling data rather than behavior, create isolated microservices that do not represent the domain accurately, or implement design patterns without a clear purpose. As with software architecture, where we need to understand what we need to do, the why or motivation is indeed more crucial than how to do it. The strategic DDD guides on this journey to deliver better software. Despite more than two decades since Eric Evans introduced DDD, many teams still make the same mistake: they treat it as a technical recipe rather than a way of thinking. Strategic DDD is what transforms DDD from a set of patterns into a language for collaboration. It's what connects software design to business vision — and without it, even the most elegant code cannot deliver real value. The Four Pillars of Strategic DDD Strategic domain-driven design can be understood through four fundamental parts: domain and subdomains definition, ubiquitous language, bounded contexts, and context mapping. These elements form the foundation for aligning business understanding with software design, transforming complex organizations into manageable, meaningful models. 1. Domain and Subdomains Definition The first step in strategic modeling is to define your domain, which refers to the scope of knowledge and activities that your software intends to address. Next, we apply the age-old strategy of "divide and conquer," a principle used by the Romans that remains relevant in modern software development. We break down the larger domain into smaller, focused areas known as subdomains. Each subdomain has its own nature and strategic importance: Core subdomain – Unique to the company, defining its identity and competitive advantage.Supporting subdomain – Supports the core business activities but does not directly create differentiation.Generic subdomain – Common across organizations, representing standardized or commodity processes. These classifications are not universal; they depend entirely on the business context. For instance, in an e-commerce platform, payments are typically a supporting subdomain, often integrated via a third-party API. But for a payment provider like Stripe, payments represent the core subdomain — the company's essence. Recognizing which subdomains drive your organization's unique value helps prioritize where to invest your best engineering efforts. The core subdomain deserves focus, innovation, and the most capable team. 2. Ubiquitous Language Once subdomains are clear, the next challenge is communication. DDD emphasizes shared understanding — a language that unites developers and domain experts. Words matter, and meanings often differ depending on the speaker's background. Take Ajax as an example: it could refer to a cleaning product, a soccer club, or the once-famous web technology. The same word can lead to three different conversations. Building a ubiquitous language means aligning terminology so that every concept has one meaning — precise and consistent within its subdomain. This shared vocabulary becomes the foundation for clear discussions, accurate models, and meaningful software. 3. Bounded Contexts Once the language is aligned, the next step is to define bounded contexts. These are explicit boundaries that indicate where a particular model and language apply. Each bounded context encapsulates a subset of the ubiquitous language and establishes clear borders around meaning and responsibilities. Although the term is often used in discussions about microservices, it actually predates that movement. Bounded contexts help clarify the meanings of terms used in your code, such as "Order," "Customer," or "Payment," especially during communication between engineers and the product team. 4. Context Mapping After defining each bounded context, the final step is to understand how they interact. That's where context mapping comes into play. It visualizes the relationships between contexts — who depends on whom, how data flows, and where translation between models is needed. Context mapping helps teams identify integration points, communication styles, and ownership responsibilities. It's a crucial step to prevent misalignment, duplication, and endless coordination problems between teams. Strategic DDD Patterns The strategy provides a set of seven patterns to guide collaboration between bounded contexts. These patterns define how teams, systems, and models interact across organizational boundaries. With this path clear, these patterns help you choose the right relationship for each context — balancing autonomy, trust, and shared responsibility. 1. Shared Kernel – A Common Ground Sometimes, two teams must work so closely that they share a small part of their model. This shared kernel represents the portion of the domain that both rely on, demanding strong communication and mutual trust. Example: In a financial system, the Compliance and Transaction contexts might share the definition of a CustomerIdentity object that includes verified user information. Any modification requires coordination among those teams; you can think of it as a library shared between domains. 2. Customer–Supplier In this relationship, the downstream (customer) depends on the upstream (supplier). The downstream can influence priorities or request changes, but the upstream retains control over its model. Example: In an e-commerce platform, the Order Management context (customer) depends on the Inventory context (supplier) to confirm product availability. If inventory changes frequently, the order team can request new API features — such as batch stock validation — influencing the supplier’s roadmap while maintaining separate models. 3. Conformist The conformist pattern occurs when the downstream does not control or influence the upstream model, but it happens often. The downstream must fully adapt to the upstream’s design, accepting its structure as-is and sometimes combining with other patterns, such as the Anticorruption layer, that we will cover soon. Example: An online marketplace integrating with a payment gateway (like Stripe or PayPal) typically acts as a conformist. The e-commerce platform must conform to the payment provider’s API and cannot dictate changes in its design or terminology. 4. Anticorruption Layer (ACL) To avoid polluting its model with foreign concepts, a downstream context can use an anticorruption layer to translate between external models and its own. This layer protects the integrity of the internal domain language. You can view this as a wrapper or adapter layer to protect your business from external services or parties. Example: A card management integration must handle several Card flags; instead of incorporating the terminology of each API into the system, it translates these into the domain. 5. Published Language When multiple teams need to integrate, they can agree on a published language — a formal, shared contract or schema that defines how data is exchanged. It’s similar to an API specification or message schema that both sides commit to. You can view this as a specification, but within an organization, for example, it relates to how money is handled. Example: In an e-commerce ecosystem, Order Management and Shipping teams might agree on a shared JSON schema called OrderDispatchedEvent. Both systems integrate using the same data structure. 6. Separate Ways For some areas, it does not make sense to have an integration. In that case, contexts can evolve separately, without any integration, each focusing on its own goals without direct coordination. Example: Some e-commerce might have the core far from the marketing and social media integration. 7. Open Host Service An open host service allows an upstream context to expose a straightforward and extensible API or protocol that many downstream systems can consume without tight coupling. It’s a well-defined entry point for collaboration. Example: A Payment Service in a fintech ecosystem provides an Open Host API that allows partners to initiate payments, check transaction status, and handle refunds. Each downstream system (like invoicing, reporting, or merchant dashboards) can consume the same API without requiring one-on-one coordination. Strategic patterns go beyond simple communication and integration. Choosing the correct pattern determines how contexts depend on each other — whether they share responsibility, coordinate loosely, or evolve independently. Mastering these relationships is what keeps software ecosystems healthy as they grow. It is super valid from an architectural perspective as well. Conclusion When we talk about domain-driven design, we often go to the code, generating a huge mistake, because we forget to go to the crucial part: the Strategic domain-driven design is not just an architectural exercise — it’s about shaping collaboration, defining boundaries, and ensuring that software reflects the business, not the database. Yet, it remains the most neglected side of DDD. Many teams jump straight into code, skipping the conversations, maps, and models that give meaning to every line they write. Of course, in an article like this, it’s impossible to explore every nuance of strategic DDD — from context mapping decisions to how communication patterns evolve within growing organizations. That’s why I decided to go deeper into this topic in my new book, Domain-Driven Design with Java. In the book, I explore in detail how strategic and tactical design connect, how to identify subdomains, define clear bounded contexts, and model relationships across complex systems — all with practical Java examples. If this article gave you a glimpse of why strategic DDD matters, the book will take you on the complete journey.

By Otavio Santana DZone Core CORE
How to Build an MCP Server and Client With Spring AI MCP
How to Build an MCP Server and Client With Spring AI MCP

If it’s spring, it’s usually conference time in Bucharest, Romania. This year was, as always, full of great speakers and talks. Nevertheless, Stephan Janssen’s one, where the audience met the Devoxx Genie IntelliJ plugin he has been developing, was by far my favorite. The reason I mention it is that during his presentation, I heard about Anthropic’s Model Context Protocol (MCP) for the first time. Quite late though, considering it was released last year in November. Anyway, to me, the intent of standardizing how additional context could be brought into AI applications to enrich and enhance their accuracy was basically what’s been missing from the picture. With this aspect in mind, I have been motivated enough to start studying about MCP and to experiment with how its capabilities can improve AI applications. In this direction, from high-level concepts to its practical use when integrated in AI applications, MCP has really caught my attention. The result: a series of articles. The first one — Enriching AI with Real-Time Insights via MCP — provided general insights regarding MCP and its architecture. It also exemplified how Claude Desktop can leverage it to gain access to real-time web search only via configuration and a dedicated MCP server plug-in. The second — Turn SQL into Conversation: Natural Language Database Queries With MCP — showed how PostgreSQL MCP Server can access private databases and enable LLMs to inspect them and offer useful pieces of information. Very little to no code was written in these two, and still ,the outcome obtained was quite promising. In the third article, How to Build an MCP Server with Java SDK, an identical database to the one in the previous article was used, but this time the MCP server was developed from scratch, using only the Java SDK. Basically, it exposed several tools that allowed the AI assistant to access an external system via MCP over stdio transport and fetch accurate context as needed. This article, the fourth, is the most complex one in the series in terms of the code written, as it exemplifies an end-to-end use case, and the components are developed from scratch: An MCP server that connects to a PostgreSQL database and exposes tools that deliver pieces of information to a peer MCP clientAn AI chat client integrated with OpenAI, which, by enclosing an MCP client, allows enriching the context with data provided by the MCP Server Both are web applications, developed using Spring Boot, Spring AI, and Spring AI MCP. In terms of MCP, the transport layer, the entity responsible for handling the communication between the client and the server is HTTP and Server-Sent Events (HTTP + SSE), which holds a stateful 1:1 connection between the two. Concerning the actual data that enriches the context, it resides in a simple PostgreSQL database schema. The access is accomplished via the great and lightweight asentinel-orm open-source tool. Being built on top of Spring JDBC and possessing most of the features of a basic ORM, it fits nicely into the client application. Use Case Working in the domain of telecom expense management, the experiment in this article uses data that models telecom invoices. Let’s assume a user can access a database that contains a simple schema with data related to these. The goal is to use the AI chat client and ask the LLM to compile several key insights about particular invoices, insights that may be further useful when compiling business decisions. Considering the PostgreSQL database server is up and running, one may create this simple schema. SQL create schema mcptelecom; Everything is simplified, so it’s easier to follow. There is only one entity — Invoice — while its attributes are descriptive and straightforward. The database initialization can be done with the script below. SQL drop table if exists invoices cascade; create table if not exists invoices ( id serial primary key, number varchar not null unique, date date not null, vendor varchar not null, service varchar not null, status varchar not null, amount numeric(18, 2) default 0.0 not null ); Although not much, the following experimental data is more than enough for the exemplification here; nevertheless, one may add more or make modifications, as appropriate. SQL insert into invoices (number, date, vendor, service, status, amount) values ('vdf-voip-7', '2025-07-01', 'VODAFONE', 'VOIP', 'REVIEWED', 157.50); insert into invoices (number, date, vendor, service, status, amount) values ('vdf-int-7', '2025-07-01', 'VODAFONE', 'INTERNET', 'PAID', 23.50); insert into invoices (number, date, vendor, service, status, amount) values ('org-voip-7', '2025-07-01', 'ORANGE', 'VOIP', 'APPROVED', 146.60); insert into invoices (number, date, vendor, service, status, amount) values ('org-int-7', '2025-07-01', 'ORANGE', 'INTERNET', 'PAID', 30.50); insert into invoices (number, date, vendor, service, status, amount) values ('vdf-voip-8', '2025-08-01', 'VODAFONE', 'VOIP', 'PAID', 135.50); insert into invoices (number, date, vendor, service, status, amount) values ('vdf-int-8', '2025-08-01', 'VODAFONE', 'INTERNET', 'APPROVED', 15.50); insert into invoices (number, date, vendor, service, status, amount) values ('org-voip-8', '2025-08-01', 'ORANGE', 'VOIP', 'REVIEWED', 147.60); insert into invoices (number, date, vendor, service, status, amount) values ('org-int-8', '2025-08-01', 'ORANGE', 'INTERNET', 'PAID', 14.50); Briefly, there are invoices from two vendors, from July and August 2025, on two different services —VOIP and Internet — some of them still under review, others approved or paid. There is no doubt that without “connecting” the OpenAI LLM with the private database, the assistant cannot be of much help, as it has no knowledge about the particular invoices. As previously stated, the goal is to put these in relation via MCP, more precisely by developing an MCP server that exposes tools through which the LLM knowledge could be enriched. Then, the MCP server is to be used by the MCP client as needed. According to the documentation [Resource 3], “Java SDK for MCP enables standardized integration between AI models and tools.” This is exactly what’s aimed here. Developing the MCP Server The purpose is to develop an MCP Server that can read pieces of information about the invoices located in the PostgreSQL database. Once available, the server is checked using the MCP Inspector [Resource 2], a very useful tool for testing or debugging such components. Eventually, the MCP Server is used by the MCP Client described in the next section via HTTP+SSE. The server project set-up is the following: Java 21Maven 3.9.9Spring Boot – 3.5.3Spring AI – v. 1.0.0PostgreSQL Driver – v. 42.7.7Asentinel ORM – v. 1.71.0 The project is named mcp-sb-server and to be sure of the recommended spring dependencies used, the spring-ai-bom configured in the pom.xml file. XML <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> The leading dependency of this project is the Spring AI MCP Server Boot Starter that comes with the well-known and convenient capability of automatic components’ configuration, which easily allows setting up an MCP server in Spring Boot applications. XML <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> As the communication is over HTTP, the WebMVC server transport is used. The starter activates McpWebMvcServerAutoConfiguration and provides HTTP-based transport using Spring MVC and automatically configured SSE endpoints. It also brings into the picture an optional STDIO transport (through McpServerAutoConfiguration) which can be enabled or disabled via the spring.ai.mcp.server.stdio property, nevertheless, it will not be used here. In order to be able to read the PostgreSQL database schema, the designated postgresql dependency is added, together with the ORM tool. spring-boot-starter-jdbc is present to ensure the automatic DataSource configuration. XML <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.asentinel.common</groupId> <artifactId>asentinel-common</artifactId> <version>1.71.0</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> As I already mentioned in a previous article, I see the MCP servers’ implementation split very clearly into two sections. The former is an MCP-specific one that is pretty similar irrespective of the particular tools’ details, while the latter focuses on the actual functionality that is almost unrelated to MCP. To configure the MCP Server, a few properties prefixed by spring.ai.mcp.server are added into the application.properties file. Let’s take them in order. Properties files spring.ai.mcp.server.name=mcp-invoice-server spring.ai.mcp.server.type=sync spring.ai.mcp.server.version=1.0.0 spring.ai.mcp.server.instructions=Instructions - SSE endpoint: /mcp/invoices/sse, SSE message endpoint: /mcp/invoices/messages spring.ai.mcp.server.sse-message-endpoint=/mcp/invoices/messages spring.ai.mcp.server.sse-endpoint=/mcp/invoices/sse spring.ai.mcp.server.capabilities.tool=true spring.ai.mcp.server.capabilities.completion=false spring.ai.mcp.server.capabilities.prompt=false spring.ai.mcp.server.capabilities.resource=false In addition to the server’s name and type, which are obvious, the ones that designate the version and the instructions are pretty important. The version of the instance is sent to clients and used for compatibility checks, while the instructions property provides guidance upon initialization and allows clients to get hints on how to utilize the server. spring.ai.mcp.server.sse-message-endpoint is the endpoint path for Server-Sent Events (SSE) when using web transports, while spring.ai.mcp.server.sse-endpoint is the one the MCP client will use as the communication endpoint. Later in the article, we will see how an HTTP-based session for sending messages is created and how async responses are processed while sending POST JSON requests. The last four properties in the above snippet define the server capabilities. Here, only tools are exposed. Next, a ToolCallbackProvider is registered, which communicates to Spring AI the beans that are exposed as MCP services. A MethodToolCallbackProvider implementation is configured, which builds instances from @Tool annotated methods. Java @Configuration public class McpConfig { @Bean public ToolCallbackProvider toolCallbackProvider(InvoiceTools invoiceTools) { return MethodToolCallbackProvider.builder() .toolObjects(invoiceTools) .build(); } } The tools’ configuration is further implemented in the component below. Java @Component public class InvoiceTools { private final InvoiceService invoiceService; public InvoiceTools(InvoiceService invoiceService) { this.invoiceService = invoiceService; } @Tool(name = "get-invoices-by-pattern", description = "Filters invoices by the provided pattern") public List<Invoice> invoicesBy(@ToolParam(description = "The pattern looked up when filtering") String pattern) { return invoiceService.findByPattern(pattern); } } We specify the name and the description of the tool, together with its parameters, if any. The tool in this MCP server is very simple; it returns invoices by a pattern that is looked up in their number attribute. The result is a list of database entities that are further sent to the client application and used by the LLM to have a better view of the context. With this, the MCP server-specific implementation is completed. Although straightforward with only a few lines of code needed, conceptually, there is quite a lot to cover and configure. Spring in general and Spring AI MCP in particular seem magical (it actually is!), but a thorough understanding of the concepts is needed once the enthusiasm passes, so that the developed applications are robust enough and ready for production. In order to complete the implementation and Invoice entities delivered to clients via MCP, an InvoiceService is developed. Data source properties are set in application.properties file. Properties files spring.datasource.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=mcptelecom spring.datasource.username=${POSTGRES_USER} spring.datasource.password=${POSTGRES_PASSWORD} The Invoice entities are mapped over the aforementioned Invoices table and modeled as below. Java import com.asentinel.common.orm.mappers.Column; import com.asentinel.common.orm.mappers.PkColumn; import com.asentinel.common.orm.mappers.Table; @Table("Invoices") public class Invoice { public static final String COL_NUMBER = "number"; @PkColumn("id") private int id; @Column(value = COL_NUMBER) private String number; @Column("date") private LocalDate date; @Column("vendor") private Vendor vendor; @Column("service") private Service service; @Column("status") private Status status; @Column("amount") private double amount; public enum Vendor { VODAFONE, ORANGE } public enum Service { VOIP, INTERNET } public enum Status { REVIEWED, APPROVED, PAID } ... } InvoiceService declares a single method, the one invoked above by the get-invoices-by-pattern tool. Java import com.asentinel.common.orm.OrmOperations; @Service public class InvoiceService { private final OrmOperations orm; public InvoiceService(OrmOperations orm) { this.orm = orm; } public List<Invoice> findByPattern(String pattern) { return orm.newSqlBuilder(Invoice.class) .select() .where() .column(Invoice.COL_NUMBER).like('%' + pattern + '%') .exec(); } } Ultimately, in order to use an OrmOperations instance and inject it into the service, it shall first be configured. Java @Configuration public class OrmConfig { @Bean public JdbcFlavor jdbcFlavor() { return new PostgresJdbcFlavor(); } @Bean public JdbcOperations jdbcOperations(DataSource dataSource, JdbcFlavor jdbcFlavor) { PgEchoingJdbcTemplate template = new PgEchoingJdbcTemplate(dataSource); template.setJdbcFlavor(jdbcFlavor); return template; } @Bean public SqlQuery sqlQuery(JdbcFlavor jdbcFlavor, JdbcOperations jdbcOps) { return new SqlQueryTemplate(jdbcFlavor, jdbcOps); } @Bean public SqlFactory sqlFactory(JdbcFlavor jdbcFlavor) { return new DefaultSqlFactory(jdbcFlavor); } @Bean public DefaultEntityDescriptorTreeRepository entityDescriptorTreeRepository(SqlBuilderFactory sqlBuilderFactory) { DefaultEntityDescriptorTreeRepository treeRepository = new DefaultEntityDescriptorTreeRepository(); treeRepository.setSqlBuilderFactory(sqlBuilderFactory); return treeRepository; } @Bean public DefaultSqlBuilderFactory sqlBuilderFactory(@Lazy EntityDescriptorTreeRepository entityDescriptorTreeRepository, SqlFactory sqlFactory, SqlQuery sqlQuery) { DefaultSqlBuilderFactory sqlBuilderFactory = new DefaultSqlBuilderFactory(sqlFactory, sqlQuery); sqlBuilderFactory.setEntityDescriptorTreeRepository(entityDescriptorTreeRepository); return sqlBuilderFactory; } @Bean public OrmOperations orm(SqlBuilderFactory sqlBuilderFactory, JdbcFlavor jdbcFlavor, SqlQuery sqlQuery) { return new OrmTemplate(sqlBuilderFactory, new SimpleUpdater(jdbcFlavor, sqlQuery)); } } Although quite verbose at first glance, for a more thorough understanding, one might explore the configuration above in detail by referring to asentinel-orm project [Resource 5]. To check that the data is successfully retrieved from the database in accordance with the particular use case, the following simple test is run. Java @SpringBootTest class InvoiceServiceTest { @Autowired private InvoiceService invoiceService; @Test void findByPattern() { var pattern = "voip"; List<Invoice> invoices = invoiceService.findByPattern(pattern); Assertions.assertTrue(invoices.stream() .allMatch(i -> i.getNumber().contains(pattern))); } } At this point, the mcp-sb-server implementation is finished and ready to be run on port 8081. Testing the MCP Server With MCP Inspector As already stated, MCP Inspector is an excellent tool for testing and debugging MCP servers. Its documentation clearly describes the needed prerequisites to run it and provides details on the available configurations. It can be started with the following command. PowerShell C:\Users\horatiu.dan>npx @modelcontextprotocol/inspector Starting MCP inspector... Proxy server listening on localhost:6277 Session token: 3c672c3389d66786f32ffe2f90d6d2116634bef316a09198fb6e933a5eeefe2b Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth MCP Inspector is up and running at: http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3c672c3389d66786f32ffe2f90d6d2116634bef316a09198fb6e933a5eeefe2b Once the MCP Inspector is up and running, it can be accessed using the above link. Prior to connecting to the developed MCP server, though, there are some prerequisites: Transport Type: SSEURL: http://localhost:8081/mcp/invoices/sse Once successfully connected, one can observe the following in the mcp-sb-server logs, which means the session has been created. Plain Text [mcp-sb-server] [nio-8081-exec-2] i.m.s.t.WebMvcSseServerTransportProvider : Creating new SSE connection for session: ffdd13e8-ad1f-4e5d-9c0a-ad001d4081f6 [mcp-sb-server] [nio-8081-exec-2] i.m.s.t.WebMvcSseServerTransportProvider : Session transport ffdd13e8-ad1f-4e5d-9c0a-ad001d4081f6 initialized with SSE builder [mcp-sb-server] [nio-8081-exec-5] i.m.server.McpAsyncServer : Client initialize request - Protocol: 2025-06-18, Capabilities: ClientCapabilities[experimental=null, roots=RootCapabilities[listChanged=true], sampling=Sampling[]], Info: Implementation[name=mcp-inspector, version=0.16.2] [mcp-sb-server] [nio-8081-exec-5] i.m.s.t.WebMvcSseServerTransportProvider : Message sent to session ffdd13e8-ad1f-4e5d-9c0a-ad001d4081f6 Next, the tools can be listed and also tried out. The picture below exemplifies the execution of get-invoices-by-pattern tool, which returns two invoices when the provided pattern is voip-7. Testing the MCP Server Usage via SSE and JSON-RPC Over HTTP At the beginning of the MCP server implementation, the SSE endpoints were set in the application.properties file. Properties files spring.ai.mcp.server.sse-message-endpoint=/mcp/invoices/messages spring.ai.mcp.server.sse-endpoint=/mcp/invoices/sse When sending HTTP POST requests, the server uses Server-Sent Events for session-based communication, and asynchronous responses may be observed in the browser, once the session exists. With the MCP server running, a session is created by invoking the designated endpoint from the browser. Plain Text http://localhost:8081/mcp/invoices/sse In the server logs, the following lines appear: Plain Text [mcp-sb-server] [nio-8081-exec-7] i.m.s.t.WebMvcSseServerTransportProvider : Creating new SSE connection for session: 7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 [mcp-sb-server] [nio-8081-exec-7] i.m.s.t.WebMvcSseServerTransportProvider : Session transport 7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 initialized with SSE builder And the response is shown in the browser: Plain Text id:7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 event:endpoint data:/mcp/invoices/messages?sessionId=7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 One may observe that data is populated with exactly the endpoint configured above, together with the sessionId request parameter. If sending an initialization request: Plain Text POST http://localhost:8081/mcp/invoices/messages?sessionId=7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 Accept: application/json { "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "clientInfo": { "name": "Exploratory MCP Client", "version": "1.0.0" } } } The server logs display: Plain Text [mcp-sb-server] [io-8081-exec-10] i.m.server.McpAsyncServer : Client initialize request - Protocol: 2024-11-05, Capabilities: null, Info: Implementation[name=Exploratory MCP Client, version=1.0.0] [mcp-sb-server] [io-8081-exec-10] i.m.s.t.WebMvcSseServerTransportProvider : Message sent to session 7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 and a new message appears in the browser window: Plain Text id:7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 event:message data:{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"tools":{"listChanged":true},"serverInfo":{"name":"mcp-invoice-server","version":"1.0.0"},"instructions":"Instructions - SSE endpoint: /mcp/invoices/sse, SSE message endpoint: /mcp/invoices/messages"} To initialize the notifications retrieval, execute: Plain Text POST http://localhost:8081/mcp/invoices/messages?sessionId=7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 Accept: application/json { "jsonrpc": "2.0", "method": "notifications/initialized" } To list the available tools, execute: Plain Text POST http://localhost:8081/mcp/invoices/messages?sessionId=7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 Accept: application/json { "jsonrpc": "2.0", "id": "1", "method": "tools/list", "params": {} } and observe the response in the browser: Plain Text id:7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 event:message data:{"jsonrpc":"2.0","id":"2","result":{"tools":[{"name":"get-invoices-by-pattern","description":"Filters invoices by the provided pattern","inputSchema":{"type":"object","properties":{"pattern":{"type":"string","description":"The pattern looked up when filtering"},"required":["pattern"],"additionalProperties":false}]} To invoke the get-invoices-by-pattern tool, execute: Plain Text POST http://localhost:8081/mcp/invoices/messages?sessionId=7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 Accept: application/json { "jsonrpc": "2.0", "id": "2", "method": "tools/call", "params": { "name": "get-invoices-by-pattern", "arguments": { "pattern": "voip-7" } } } and observe the response in the browser: Plain Text id:7bd2d41c-8643-41e6-9aa3-0b2a3e4496b4 event:message data:{"jsonrpc":"2.0","id":"2","result":{"content":[{"type":"text","text":"[{\"id\":1,\"number\":\"vdf-voip-7\",\"date\":[2025,7,1],\"vendor\":\"VODAFONE\",\"service\":\"VOIP\",\"status\":\"REVIEWED\",\"amount\":157.5},{\"id\":3,\"number\":\"org-voip-7\",\"date\":[2025,7,1],\"vendor\":\"ORANGE\",\"service\":\"VOIP\",\"status\":\"APPROVED\",\"amount\":146.6}]"}],"isError":false} Obviously, the same result as in the case of the MCP Inspector is obtained. The conclusion is that the developed MCP server is tested and works as expected; it can now be actually used. Developing the AI Chat Client It’s a minimal web application that leverages Spring AI and allows users to communicate with OpenAI. The functionality here is straightforward; however, the emphasis is on the LLM response quality within a very specific scenario, as the MCP server is plugged in. Let’s imagine a user is interested in finding out a few key insights about certain telecom invoices (for example, whose invoice numbers contain a pattern) and, moreover, restricted to a particular month of the year. Could the LLM compile such pieces of information on its own? Not quite, but that’s the reason the previous MCP server was developed: to make this context available to the LLM so that it can take it from there. Briefly, data flows in the following manner: The user issues a parameterized HTTP request – GET /assistant/invoice-insights?month=July&year=2025&pattern=vdf.The client application uses a prompt to create the previously mentioned input: "Give me some key insights about the invoices that contain ‘{pattern}’ in their number. If available, use year {year} and month {month} when analyzing."The client application sends it to the LLM, which provides its output that is further returned to the client as a response. The client project set-up is the following: Java 21Maven 3.9.9Spring Boot 3.5.3Spring AI 1.0.0 The project is named mcp-sb-client and as in the case of the server the spring-ai-bom configured in the pom.xml file. In addition to spring-ai-starter-model-openai dependency, the one of interest here is XML <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> which allows connecting to the MCP server via stdio or SSE transports. As the communication is done over HTTP, the latter is considered. The SSE connection uses the HttpClient transport implementation and for every connection to an MCP server, a new MCP client instance is created. To configure the MCP client, a few properties prefixed by spring.ai.mcp.client can be added to the application.properties file. Additionally, the following property indicates the base URL of the MCP server the client connects to, needed when constructing the HttpClientSseClientTransport. Properties files mcp.invoices.server.base-url = http://localhost:8081 As already stated, this proof of concept uses OpenAI. In order to be able to connect to the AI model, a valid API key of the user on behalf of which the communication is made should be set in the application.properties file [Resource 1]. To set a bit of the LLM boundaries and not make it too creative, the temperature parameter is configured as well. Properties files spring.ai.openai.api-key = ${OPEN_AI_API_KEY} spring.ai.openai.chat.options.temperature = 0.3 On my machine, the key is held in the designated environment variable and used when appropriate. The interaction between the user and the LLM is doable via a ChatClient, that is injected into the below AssistantService. Java @Service public class AssistantService { private final ChatClient client; public AssistantService(ChatClient.Builder clientBuilder, McpSyncClient mcpSyncClient) { client = clientBuilder .defaultSystem("You are a helpful assistant with Telecom knowledge") .defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClient)) .build(); } public String invoiceInsights(String month, String year, String pattern) { final String text = """ Give me some key insights about the invoices that contain '{pattern}' in their number? If available, use year {year} and month {month} when analyzing. """; return client.prompt() .user(userSpec -> userSpec.text(text) .param("month", month) .param("year", year) .param("pattern", pattern)) .call() .content(); } } The focus is not on how the ChatClient is used. If interested in the details, have a look at this article. The focus here is on the McpSyncClient that is packaged into a SyncMcpToolCallbackProvider instance and used when the ChatClient is build. According to the JavaDoc, a SyncMcpToolCallbackProvider has the purpose of discovering MCP tools from one or more MCP servers. It is basically the Spring AI server tool provider. Very briefly, it connects to the MCP server via a sync client, it lists and reads the available exposed server tools (one in the case of our MCP server), and creates a SyncMcpToolCallback for each. The SyncMcpToolCallback actually connects the MCP tool to Spring AI’s tool system and allows it to be executed seamlessly inside Spring AI applications. Obviously, tool calls are handled through the MCP client, which is configured as follows. Java @Configuration public class McpConfig { @Bean public McpSyncClient mcpSyncClient(@Value("${mcp.invoices.server.base-url}") String baseUrl) { var transport = HttpClientSseClientTransport.builder(baseUrl) .sseEndpoint("mcp/invoices/sse") .build(); McpSyncClient client = McpClient.sync(transport) .requestTimeout(Duration.ofSeconds(10)) .clientInfo(new McpSchema.Implementation("MCP Invoices Client", "1.0.0")) .build(); client.initialize(); return client; } } As the client application is a web one as well (running on port 8080), the AssistantService is plugged into a @RestController, to easily interact with it. Java @RestController public class AssistantController { private final AssistantService assistantService; public AssistantController(AssistantService assistantService) { this.assistantService = assistantService; } @GetMapping("/invoice-insights") public ResponseEntity<String> invoicesInsights(@RequestParam(defaultValue = "") String month, @RequestParam(defaultValue = "") String year, @RequestParam String pattern) { return ResponseEntity.ok(assistantService.invoiceInsights(month, year, pattern)); } } The MCP client and server are now connected; the outcome may be examined. The Results To use the integration, one may first run the MCP server. When the MCP Client starts as well, the following lines appear in the logs. Plain Text 2025-08-05T16:40:10.891+03:00 DEBUG 56796 --- [mcp-sb-server] [nio-8081-exec-1] i.m.s.t.WebMvcSseServerTransportProvider : Creating new SSE connection for session: c0eee498-ffdf-4f4e-bfbd-d5b683154e9b 2025-08-05T16:40:10.901+03:00 DEBUG 56796 --- [mcp-sb-server] [nio-8081-exec-1] i.m.s.t.WebMvcSseServerTransportProvider : Session transport c0eee498-ffdf-4f4e-bfbd-d5b683154e9b initialized with SSE builder 2025-08-05T16:40:10.994+03:00 INFO 56796 --- [mcp-sb-server] [nio-8081-exec-2] i.m.server.McpAsyncServer : Client initialize request - Protocol: 2024-11-05, Capabilities: ClientCapabilities[experimental=null, roots=null, sampling=null], Info: Implementation[name=MCP Invoices Client, version=1.0.0] 2025-08-05T16:40:11.006+03:00 DEBUG 56796 --- [mcp-sb-server] [nio-8081-exec-2] i.m.s.t.WebMvcSseServerTransportProvider : Message sent to session c0eee498-ffdf-4f4e-bfbd-d5b683154e9b Plain Text 2025-08-05T16:40:11.053+03:00 INFO 11104 --- [mcp-sb-client] [ient-3-Worker-2] i.m.client.McpAsyncClient : Server response with Protocol: 2024-11-05, Capabilities: ServerCapabilities[completions=null, experimental=null, logging=LoggingCapabilities[], prompts=null, resources=null, tools=ToolCapabilities[listChanged=true]], Info: Implementation[name=mcp-invoice-server, version=1.0.0] and Instructions Instructions - SSE endpoint: /mcp/invoices/sse, SSE message endpoint: /mcp/invoices/messages which demonstrates the connection between the two was successfully initialized. Let’s observe what happens when the user sends the next request to the client application, which means is interested in insights on the invoices from 2025, but only the ones having the vdf pattern contained in their number. To refresh our memory, there are four such invoices in the database. Plain Text GET http://localhost:8080/assistant/invoice-insights?year=2025&pattern=vdf The response obtained is quite interesting: Plain Text Here are the key insights about the invoices that contain 'vdf' in their number for the year 2025: 1. **Total Invoices**: There are 4 invoices that match the pattern 'vdf'. 2. **Invoice Details**: - **Invoice 1**: - **Number**: vdf-voip-7 - **Date**: July 1, 2025 - **Vendor**: VODAFONE - **Service**: VOIP - **Status**: REVIEWED - **Amount**: $157.50 - **Invoice 2**: - **Number**: vdf-int-7 - **Date**: July 1, 2025 - **Vendor**: VODAFONE - **Service**: INTERNET - **Status**: PAID - **Amount**: $23.50 - **Invoice 3**: - **Number**: vdf-voip-8 - **Date**: August 1, 2025 - **Vendor**: VODAFONE - **Service**: VOIP - **Status**: PAID - **Amount**: $135.50 - **Invoice 4**: - **Number**: vdf-int-8 - **Date**: August 1, 2025 - **Vendor**: VODAFONE - **Service**: INTERNET - **Status**: APPROVED - **Amount**: $15.50 3. **Total Amount**: The total amount for these invoices is $332.00. 4. **Status Overview**: - 2 invoices are marked as PAID. - 1 invoice is marked as REVIEWED. - 1 invoice is marked as APPROVED. 5. **Service Breakdown**: - VOIP Services: 2 invoices totaling $293.00. - INTERNET Services: 2 invoices totaling $39.00. These insights provide a clear overview of the invoices associated with 'vdf', highlighting the amounts, statuses, and service types. If recalling from the previous section, get-invoices-by-pattern MCP server tool filters invoices only by pattern, which means the context provided to the OpenAI LLM was ‘enlarged’ with the corresponding invoices. From this point on, it’s the model’s job to add its contribution to the requested analysis. As one may observe, it managed to come up with a decent solution to the user’s enquiry. Nevertheless, without the use of the MCP server, the context would have been almost empty; thus, no conclusions could have been drawn about the invoices in discussion. Final Considerations The applications presented in this article illustrate how users can benefit from a cohesive system where individual components integrate seamlessly to deliver meaningful results. With the use of MCP, more exactly by creating a 1:1 stateful connection over HTTP between the MCP server and the MCP client, data from a private database was put into context to help the OpenAI LLM provide the user with business insights related to telecom invoices. Indeed, this is a simplistic use case, focused on a single specific tool intended to illustrate the purpose. Nevertheless, one may use it as a starting point to explore how MCP works, to understand its architecture, and how MCP can enhance AI applications by leveraging its functionalities. One last aspect, though, concerns the idea of putting such services into a production environment, a place that programmers really appreciate and value. As these are Spring Web applications, they can be easily secured using Spring Security. In terms of scalability, although HTTP requests towards LLMs and databases use blocking IO, the thread usage can be significantly improved by leveraging Java 21’s virtual threads. Regarding the observability, all requests to an LLM do cost, but fortunately, Spring Boot Actuator can be quickly plugged in to monitor the metrics related to the actual token consumption, and the resource consumption can be optimized. Resources Open AI PlatformMCP InspectorMCP Java SDK DocumentationSpring AI MCP Referenceasentinel-orm projectMCP Invoice Server codeMCP Invoice Client codeThe picture was taken at Cochem Castle in Germany.

By Horatiu Dan
Build a Dynamic Web Form Using Camunda BPMN and DMN
Build a Dynamic Web Form Using Camunda BPMN and DMN

Business Process Model and Notation (BPMN) is the universal standard for visually modeling and automating business processes. It is used to design and automate workflows, defining the sequence of tasks, approvals, and user interactions. Whereas Decision Model and Notation (DMN) models the complex decision logic that can be embedded within those processes to automate business rules in a structured, reusable way. Camunda's process orchestration platform provides a collaborative environment for Business and IT developers via an intuitive visual Modeler that adheres to BPMN and DMN standards. Modeling with Camunda reduces the time it takes to develop and maintain real-world business processes through automation. Beyond automation, combining BPMN and DMN allows us to create dynamic web forms where the fields, validations, and even flow of the form adapt real-time business rules, instead of being hardcoded. This makes applications more flexible, easier to maintain, and business-driven. What Is a Dynamic Web Form A dynamic web form is an interactive form whose fields, validations, and flow are driven by business rules or user input. Instead of hardcoding logic into the UI, the form leverages decision models to display only what’s relevant, making it smarter, leaner, and easier to maintain. Think of an application that has multiple webpages collecting user input data. The traditional way of implementing these webpages is hard-coded field types, validations, and repeating the same set of tasks in multiple pages. A dynamic webform concept is built once and used multiple times. The fields and validations are configured centrally, and a single webpage renders the fields based on what the page is intended for. A Simple Use Case I will create a simple web form with a few dynamic questions. The form will start with the initiation of a BPMN process instance, and that will track different answers and flows. The DMN will have the questions configured, which will appear dynamically in the web form. Camunda Modeler will be used to create the BPMN diagram and DMN tables. The web application is built with Angular on the frontend and Spring Boot on the backend, integrating the Camunda process engine. High-Level Architecture BPMN Design The main BPMN Process, which captures the user's answers/input for different questions, has: A sub-process for questions A few script tasks for checking and setting execution variables if the user reached the last question, answer, and question-answer collection A couple of user tasks to answer the question and submit the form The sub-process, question BPMN has: A few script tasks to parse the input from the form and then prepare the next question payload to render in the formDMN table to determine the next question based on the answer/input of the previous question DMN Design The Next Question DMN has multiple tables to hold different input elements and their answer choices. Next question table – This table holds the question_id and answer as input and returns another question_id once the condition satisfiesQuestion list table – This table holds the question metadata - question_id, input type (text, radio, checkbox, etc.), question title, answer_choice_idAnswer choice table – this table holds the different answer options for radio, dropdown, checkbox, etc.Literal expression – This combines all three tables and prepares a collection of questions with answer choices.Inputs – The entry point for this DMN, questionIdInput, and answerInput, to determine the next question. This DMN is the foundation behind the dynamic nature of the form. The questions, input field types, answer choices, title, description, help text, etc. — everything is configured as a rule. The front-end UI only renders the questions dynamically, rather than hard-coding the input fields in the form itself. Application Development The next step after the BPMN and DMN design is to deploy these models in the Camunda engine and implement the frontend and backend. The frontend UI will render the dynamic form by calling Camunda native APIs for process orchestration. Backend Spring Boot with an embedded Camunda engine is used as the backend of this dynamic form application. The backend is very lightweight to deploy the main BPMN process, question BPMN process, and question DMN. Markdown ├── src/main/ │ ├── java/com/dynamicform/workflow/ │ │ └── Application.java │ └── resources/ │ ├── application.yaml │ ├── main-process.bpmn │ ├── question.bpmn │ └── next-question.dmn └── pom.xml Frontend The dynamic form renderer UI is built in Angular. Instead of hardcoding logic into Angular components, the form behavior (fields, validations, and visibility) is driven by Camunda BPMN workflows and DMN decision tables. Question metadata and details are fetched against the DMN, and JSON data is parsed in a form component to render the questions with input type, answer choices, etc. The integration between the UI and backend process engine is done through Camunda engine-rest APIs: Markdown - /engine-rest/process-definition/key/main-process/start : To start the process instance - /engine-rest/process-instance/${processId}/variables : To get the variables of a process instance - /engine-rest/task : To create a task - /engine-rest/task/${taskId}/complete : To mark a task complete I have created a simple form with a couple of questions about whether the user has breakfast, and this is how it renders based on the answers to the previous question. The form/UI page displays the questions in a dynamic manner and is based on the response to the previous question. This single UI page can render any form based on a simple configuration instead of creating multiple UI pages for different forms. Configure: Build Once and Use Multiple Times Form elements or questions can be grouped as a template/page for a specific UI page. An additional column in the question list DMN, as the template/page identifier, can hold all form elements/questions for every page. What I Have Learned This approach of configuring the form elements outside of the code undoubtedly provides flexibility in developing the UI. But most importantly, it empowers the business team to configure the process flow, form elements, rules, etc., without or with minimal help from the developer in design time, which accelerates the delivery at the end of the day.

By Nabin Debnath

Top Microservices Experts

expert thumbnail

Amol Gote

Solution Architect,
Innova Solutions (Client - iCreditWorks Start Up)

Amol Gote is a seasoned IT professional who builds scalable, resilient microservices. He has a unique experience in building microservices in .NET and Java. He is skilled in deploying and managing microservices in AWS and Azure environments and has technical proficiency across multiple applications and tools. This expertise includes full-stack capabilities, from designing to building end-to-end solutions involving databases (SQL/NoSQL), back-end services, messaging services, and modern web applications.
expert thumbnail

Ray Elenteny

Solution Architect,
SOLTECH

With over 40 years of experience in the IT industry, Ray thoroughly enjoys sharing his experience by helping organizations deliver high-quality applications that drive business value. Ray has a passion for software engineering. Over the past ten years or so, Ray has taken a keen interest in the cultural and technical dynamics of efficiently delivering applications.
expert thumbnail

Satrajit Basu

Chief Architect,
TCG Digital

Satrajit, a visionary Chief Architect and an AWS Ambassador, brings unparalleled expertise in architecting and directing mission-critical projects for industry leaders across various sectors. From banking to aviation, Global Distribution Systems (GDS) to restaurant and travel e-commerce, Satrajit has mastered the art of migrating and modernizing workloads on AWS. With an unwavering passion for technology, Satrajit ensures that applications on AWS are not just well-architected but also leverage the latest cutting-edge technologies. An architect par excellence, Satrajit's dedication extends beyond project delivery. He generously shares his vast knowledge through insightful technical blogs, enlightening aspiring architects and developers worldwide

The Latest Microservices Topics

article thumbnail
Kubernetes v1.34: Enabling Smarter Traffic Routing With PreferSameNode and PreferSameZone
Kubernetes v1.34 introduces smarter traffic routing with PreferSameNode and PreferSameZone, reducing latency, cross-zone costs, and improving efficiency.
November 7, 2025
by Nitin Ware
· 310 Views · 1 Like
article thumbnail
A Beginner's Guide to Docker Compose
Learn how to use Docker Compose to manage multi-container setups. Help overcome the need of running multiple docker-run statements to manage applications.
November 6, 2025
by Akaash Vishal Hazarika
· 1,058 Views · 2 Likes
article thumbnail
A Beginner's Guide to Essential Commands to Fix Container Setup Issues
The five essential commands that all developers should know in order to develop and build applications in Docker. This will pinpoint the exact reasons for failure.
November 6, 2025
by Akaash Vishal Hazarika
· 712 Views
article thumbnail
A Practical Guide to Modernizing Data Serving Architectures Using DBSQL
Learn how we replaced our traditional RDS serving layer with direct lakehouse querying to improve performance, reduce cost, and achieve real-time data freshness.
November 6, 2025
by PRAVEEN DOOTAGANI
· 569 Views
article thumbnail
Bridging the Divide: Tactical Security Approaches for Vendor Integration in Hybrid Architectures
Learn about real-world hybrid security use cases and learn tactical strategies for securely integrating vendor software in cloud and on-prem environments.
November 5, 2025
by Dipankar Saha
· 599 Views
article thumbnail
Monolith vs Microservices vs Modulith
Monolithic and microservices architecture have been widely adopted, while modulithic architecture has been gaining traction. Learn about their differences and benefits.
November 5, 2025
by RENJITH RAMACHANDRAN
· 647 Views
article thumbnail
Top Takeaways From Devoxx Belgium 2025
In October 2025, Devoxx Belgium showcased its 22nd edition, emphasizing Java and AI advancements. Sessions covered were AI workflows, architecture decisions, and more.
November 4, 2025
by Gunter Rotsaert DZone Core CORE
· 764 Views
article thumbnail
Detecting Supply Chain Attacks in NPM, PyPI, and Docker: Real-World Techniques That Work
Supply chain attacks represent the modern cybersecurity nightmare — attackers compromise the dependencies you trust instead of attacking you directly.
November 3, 2025
by David Iyanu Jonathan
· 1,874 Views · 2 Likes
article thumbnail
Deployable Architecture: The Cornerstone of Scalable Platform Engineering
Deployable architecture turns platform strategy into scalable execution — standardizing delivery with speed, security, and control.
October 31, 2025
by Josephine Eskaline Joyce DZone Core CORE
· 1,065 Views · 3 Likes
article thumbnail
A Comprehensive Analysis of Async Communication in Microservice Architecture
Asynchronous communication decouples microservices through message brokers like AWS SQS/SNS and Google Pub/Sub, enabling independent scaling and improved resilience.
October 30, 2025
by Aniruddha Maru
· 1,468 Views · 2 Likes
article thumbnail
From Model to Microservice: A Practical Guide to Deploying ML Models as APIs
Stop letting your machine learning models stagnate in Jupyter notebooks. To deliver real value, you need to integrate them into applications.
October 30, 2025
by Naga Sai Mrunal Vuppala
· 1,224 Views · 1 Like
article thumbnail
Building Reactive Microservices With Spring WebFlux on Kubernetes
Spring WebFlux on Kubernetes delivers high-throughput, low-latency I/O with fewer resources using reactive IO, Resilience4j, and metrics-based autoscaling.
October 30, 2025
by Mikhail Povolotskii
· 1,728 Views · 1 Like
article thumbnail
ZEISS Demonstrates the Power of Scalable Workflows With Ampere® Altra® and SpinKube
ZEISS partnered with Ampere, Fermyon, and Microsoft to cut cloud costs by 60% using SpinKube and WebAssembly for scalable, on-demand workloads.
October 29, 2025
by Scott Fulton III
· 2,235 Views · 3 Likes
article thumbnail
Kubernetes Debugging Recipe: Practical Steps to Diagnose Pods Like a Pro
Adopt step-by-step debugging with kubectl, ephemeral containers, and AI-powered RCA to find root causes faster. Add predictive autoscaling and anomaly detection.
October 24, 2025
by Chandrasekhar Rao Katru
· 2,747 Views · 5 Likes
article thumbnail
From Distributed Monolith to Composable Architecture on AWS
Learn in this article how to fix distributed monoliths with Domain-Driven Composable Architecture and build independent, composable microservices on AWS.
October 24, 2025
by Elakkiya Daivam
· 6,172 Views · 1 Like
article thumbnail
Strategic Domain-Driven Design: The Forgotten Foundation of Great Software
Most teams skip strategic DDD and focus only on code. Learn how domains, contexts, and patterns align software with business reality.
October 24, 2025
by Otavio Santana DZone Core CORE
· 1,472 Views · 3 Likes
article thumbnail
Build a Dynamic Web Form Using Camunda BPMN and DMN
Camunda BPMN and DMN can be combined to build dynamic web forms that adapt fields and flows in real time, which reduces hardcoding and improves flexibility.
October 24, 2025
by Nabin Debnath
· 1,458 Views · 4 Likes
article thumbnail
How to Build an MCP Server and Client With Spring AI MCP
Benefit from a cohesive system where individual components, such as LLM, MCP server, and MCP client, integrate seamlessly to deliver meaningful results.
October 23, 2025
by Horatiu Dan
· 2,114 Views · 2 Likes
article thumbnail
A Practical Guide to Deploying Microservices on Kubernetes
This guide covers deploying microservices on Kubernetes, highlighting containerization, scaling, monitoring, and security.
October 21, 2025
by Puneet Pahuja
· 1,738 Views · 1 Like
article thumbnail
Coarse Parallel Processing of Work Queues in Kubernetes: Advancing Optimization for Batch Processing
In the current modern distributed architecture, this article will explain how to execute a Kubernetes Job with optimized and multiple parallel worker processes.
October 20, 2025
by Tanu Jain
· 1,848 Views · 1 Like
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • ...
  • Next

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: