Recent Posts
Archives

Posts Tagged ‘Microservices’

PostHeaderIcon [VoxxedDaysBucharest2026] Ports, Adapters, and the Independence of Business Logic: George Patrașcu on Hexagonal Architecture in Practice

Lecturer

George Patrașcu is a seasoned software engineer at eMAG/CTO with more than twenty years of professional experience, including significant time in architectural leadership positions. Currently contributing to the Invoice and Payments platform team, George plays an active role in shaping internal developer guidelines and promoting sound architectural practices throughout a large organization characterized by hundreds of autonomous teams and diverse technology stacks.

Abstract

Within expansive microservices ecosystems featuring autonomous teams, frequent deployments, and heterogeneous technologies, business logic commonly becomes entangled with infrastructure specifics, yielding systems that are difficult to maintain, evolve, or understand. George Patrașcu presents Hexagonal Architecture—also recognized as Ports and Adapters—as a pragmatic methodology for protecting core domain logic from external dependencies including relational databases, RESTful services, event streams such as Kafka, and various third-party integrations. Grounded firmly in production realities at eMAG, the session provides balanced coverage of conceptual foundations, detailed C# implementation examples, testing approaches, and honest discussion of trade-offs encountered in practice.

The Challenges of Traditional Layered Architectures in Evolving Systems

Conventional layered architectures consisting of presentation, business, and data access tiers deliver initial value for straightforward applications. However, sustained growth across hundreds of teams utilizing varied languages (Java, .NET, Python, Scala), communication mechanisms (REST, Kafka), and continuous integration practices exposes fundamental weaknesses.

Business rules gradually permeate multiple layers: service classes reference persistence entities directly, controllers embed data access logic, and external service contracts influence domain models. Bounded contexts, a cornerstone of Domain-Driven Design, demand careful translation through anti-corruption layers, yet traditional designs frequently fail to maintain clean separation.

Resulting issues include duplicated business logic across backend-for-frontend components, mobile client platforms, and core services; severely compromised unit testing due to pervasive infrastructure dependencies; and cascading changes whenever external contracts, database schemas, or framework versions evolve. Simple folder structures offer no enforceable boundaries, while multi-module projects introduce tedious mapping layers that increase cognitive load.

Core Concepts of Hexagonal Architecture: Ports, Adapters, and the Application Core

Hexagonal Architecture fundamentally inverts dependency direction to position the domain model at the center. Business logic and associated domain entities reside within the core, depending exclusively upon abstract ports rather than concrete implementations. Input ports, implemented by driving adapters (REST controllers, message consumers), expose use cases to external actors. Output ports, realized by driven adapters (repositories, external clients), allow the core to interact with infrastructure without awareness of underlying details.

This structure adheres strictly to the Dependency Inversion Principle: high-level policy (domain) remains independent of low-level mechanisms (infrastructure). The hexagonal representation visually encapsulates the core, with ports serving as interfaces through which adapters connect. Domain objects and language remain pure, expressed in business terms rather than technical artifacts.

Flexible structuring accommodates varying scales: monolithic single projects for initial simplicity, separation of adapters by technical domain (isolating payment processors or external APIs), or evolution from modular monoliths toward independent microservices. This “build modular from the start” philosophy supports rapid feature development followed by natural service extraction when boundaries emerge.

Implementation Details, Trade-offs, and Testing Strategies

Practical development begins with domain modeling. For a rescue fleet management system, an AssembleFleet input port defines the primary use case contract. Implementation within a domain service orchestrates inventory retrieval and selection logic expressed purely in business concepts.

Driven adapters address external complexities: a Swappy client manages API pagination, performs type coercions (string passenger counts to domain integers), and implements anti-corruption filtering for inconsistent partner data. Custom domain annotations (@DomainService) facilitate integration with dependency injection frameworks without introducing technical concerns into the core.

Persistence strategies favor rich domain models mapped via ORM capabilities (e.g., shadow properties, complex types), minimizing dual maintenance of entities. This preserves core purity while leveraging framework strengths.

Testing follows clear separation: exhaustive unit tests exercise domain logic in complete isolation, while integration tests utilize fakes and mocks for adapters, verifying translation correctness without external system dependencies.

Trade-offs warrant careful consideration. The pattern excels with rich domain models containing substantial behavior but may introduce unnecessary indirection for straightforward query-dominant services. Hybrid approaches applying hexagonal principles selectively to complex logic paths, combined with CQRS separation or vertical slice organization, often prove optimal. The speakers caution against architectural dogmatism—context, team maturity, and problem complexity should guide application extent.

Architectural fitness functions, implemented via tools like ArchUnit, provide automated verification of boundaries even when AI assistance generates code.

Implications for Autonomous Teams and Long-Term Maintainability

Within large organizations featuring hundreds of developers operating with significant autonomy and minimal centralized control, Hexagonal Architecture strikes an effective balance. Teams retain freedom in implementation details while benefiting from consistency promoted through architecture guilds and shared guidelines. The pattern facilitates technology migration, incremental refactoring, and clear delineation of responsibilities.

By maintaining domain logic independent of infrastructure specifics, services demonstrate greater resilience to external changes—whether API contract updates, database platform shifts, or new integration requirements. This aligns naturally with trunk-based development, feature flag strategies, and high-frequency deployment practices, enhancing overall organizational agility.

Not every service necessitates complete hexagonal purity. Selective application targeting areas of highest coupling delivers outsized returns in testability, evolvability, developer onboarding speed, and long-term maintenance costs.

Links:

PostHeaderIcon [MunchenJUG] Advanced Automated Testing: Navigating the Integration Frontier (13/May/2024)

Lecturer

Daniel Istvan Buza is an accomplished Senior Software Engineer and Technical Lead with extensive experience in architecting Java-based ecosystems. His expertise spans a broad spectrum of technologies, including Spring, Angular, Kafka, MongoDB, and Microservices. As a leader of multiple development teams, Daniel focuses on enhancing code quality through initiatives like coding dojos and rigorous peer reviews. He is a dedicated mentor within the software community, constantly exploring innovative methodologies to bridge the gap between development and quality assurance.

Abstract

This article analyzes the transition from traditional unit testing to comprehensive acceptance and end-to-end (E2E) testing frameworks. It utilizes a real-world case study of a “silent” frontend-backend failure to illustrate why high test coverage often fails to detect integration defects. The discussion centers on the implementation of Playwright as a primary tool for stateful and stateless testing within a Java environment. By evaluating strategies for mocking external dependencies like Kafka and S3, and addressing common pitfalls such as internationalization and time zone sensitivities, this analysis provides a technical roadmap for building resilient CI/CD pipelines that go beyond the limitations of isolated component tests.

The Vulnerability of Isolated Testing

A critical challenge in modern web development is the “integration gap”—a scenario where backend and frontend components pass their respective unit tests but fail when operating in tandem. A common example involves dynamic attribute renaming: if a backend developer renames a field in a DTO (Data Transfer Object) and updates the corresponding tests, the backend remains “green.” However, if the frontend is not simultaneously updated to expect the new attribute name, the UI may fail to display data correctly, resulting in empty columns or broken features that are highly visible to users but invisible to isolated test suites.

This discrepancy highlights a fundamental truth: test coverage does not equate to test quality. Even 100% coverage cannot guarantee system correctness if the interactions between disparate services are not explicitly verified. To address this, teams must move toward “Acceptance Tests” (AC tests) that simulate actual user interactions across the entire stack.

Leveraging Playwright for Java-Centric Environments

For teams primarily composed of backend developers, selecting a testing tool that integrates seamlessly with the existing Java ecosystem is paramount. Playwright, a framework developed by Microsoft, has emerged as a robust solution due to its native Java support and its ability to automate browsers like Chrome, Firefox, and Safari.

Core Interactions in E2E Testing

The majority of web application functionality can be verified through four fundamental interaction types:

  1. Clicking: Interacting with buttons, links, and navigation elements.
  2. Input: Filling text and search fields.
  3. Assertion: Verifying visual properties, such as the presence, color, or size of elements.
  4. File Operations: Managing the upload and download of documents.

By focusing on these interactions, developers can create scripts that mirror user behavior, ensuring that the “happy path” of the application remains functional regardless of internal refactoring.

Architectural Strategies: Stateful vs. Stateless Testing

When implementing E2E tests, developers must choose between two primary architectural approaches: stateful and stateless testing.

Stateful (Environment-Targeted) Testing

In this model, tests are executed against a persistent environment with a shared database and live external APIs. This approach is highly realistic but introduces the risk of “pollution,” where data left by one test affects the outcome of subsequent tests. It requires rigorous cleanup procedures to maintain environment stability.

Stateless (Containerized) Testing

Stateless testing involves spinning up a fresh, full-featured frontend-backend pair within a CI/CD pipeline for every test run. This often utilizes embedded databases (e.g., MongoDB) and mocks for external dependencies like S3 buckets or Kafka topics. While more complex to set up, this method provides total isolation and reproducibility. However, it requires careful management of operating system dependencies within the test containers to ensure Playwright can execute the browsers correctly.

Technical Pitfalls and Best Practices

The transition to advanced automated testing reveals several subtle challenges that can undermine test reliability.

  • Internationalization (i18n): Relying on UI text for selectors can lead to massive test failures when translation files are updated. Using unique element IDs is a safer alternative, though it may limit the ability to verify that the correct error messages are being displayed to the user.
  • Time Zone Sensitivity: UI elements displaying timestamps will vary based on the local environment. Playwright allows developers to explicitly specify a locale to ensure consistent assertions across geographically distributed teams.
  • Synchronicity: A major source of test flakiness is the misuse of Thread.sleep(). Developers should instead utilize Playwright’s built-in “wait for condition” methods to handle asynchronous backend tasks, which are more resilient to varying network or processing speeds.

Conclusion

Modern software delivery requires a testing strategy that transcends the unit level. By integrating Playwright into the Java development lifecycle, teams can automate complex user journeys and bridge the gap between frontend and backend. While no single testing setup is superior, a combination of stateful environment checks and isolated CI/CD pipelines provides the most comprehensive defense against integration defects. Developers are encouraged to treat their test code with the same rigor as production code, striving for cleanliness and maintainability to ensure long-term system reliability.

Links:

PostHeaderIcon [MunchenJUG] Strategic API Communication: Enhancing Interaction Between Providers and Consumers (4/Nov/2024)

Lecturer

Enis Spahi is a software architect and consultant with extensive experience in designing and implementing large-scale distributed systems. He is a specialist in API design, microservices architecture, and contract-driven development. Enis is recognized for his contributions to the community regarding API governance and the standardization of machine-to-machine communication. His professional focus involves streamlining the collaboration between backend service providers and frontend or third-party consumers, advocating for “API-First” and “Consumer-Driven” methodologies to reduce integration friction.

Abstract

While APIs are fundamentally engineered for machine-to-machine communication, their development is deeply influenced by human factors, including discoverability, documentation, and interpersonal coordination. This article explores the methodologies for enhancing provider and consumer interaction through standardized specification languages and contract testing. By analyzing the transition from “Code-First” to “API-First” and “Consumer-First” approaches, the discussion highlights the innovations brought by OpenAPI, AsyncAPI, and Pact. The analysis further evaluates the technical implications of automated documentation and contract verification in maintaining system integrity within microservices ecosystems.

The Human Challenge in Technical Interfaces

The primary bottleneck in modern software delivery is often not the implementation of logic, but the communication of how that logic can be accessed. Enis Spahi identifies a recurring problem in the industry: the lack of API discoverability. Even the most technically sound API is useless if a potential consumer cannot find it or understand its requirements. This “Communication Gap” often leads to wasted development cycles, where teams build redundant services or struggle with mismatched expectations.

To address this, the methodology shifts from viewing an API as a technical byproduct to viewing it as a Product. This perspective necessitates a commitment to high-quality documentation and a “Common Language” that both providers and consumers can use to negotiate the interface’s behavior.

Standardization via Specification Languages

A cornerstone of modern API communication is the use of standardized specification languages. These formats provide a machine-readable “source of truth” that can be transformed into human-readable documentation or even executable code.

  • OpenAPI (formerly Swagger): This has become the de facto standard for RESTful APIs. It allows providers to define endpoints, request/response formats, and security requirements in a YAML or JSON file.
  • AsyncAPI: As architectures move toward event-driven patterns, AsyncAPI provides the same level of rigor for asynchronous communications (e.g., Kafka, RabbitMQ), defining message formats and channel structures.
  • Documentation as Code: By maintaining specifications in version control, documentation becomes a living asset. Tools can automatically generate interactive portals (like Swagger UI) where consumers can explore and test the API in real-time.

Comparative Methodologies: Code-First vs. API-First vs. Consumer-First

The strategy chosen for API development significantly impacts the relationship between the provider and the consumer.

  1. Code-First: Implementation begins immediately, and the specification is generated from the code. While fast for small teams, this often leads to “leaky abstractions,” where internal implementation details are inadvertently exposed to consumers.
  2. API-First: The specification is designed and agreed upon before any code is written. This allows frontend and backend teams to work in parallel, using the specification to generate mocks. It fosters a more deliberate and consumer-friendly design.
  3. Consumer-First (Contract Testing): This methodology, exemplified by tools like Pact, takes collaboration a step further. Consumers define their expectations in a “contract.” The provider then verifies its implementation against these contracts. This ensures that a provider never makes a change that would break an existing consumer.

Code Sample: A Simple Pact Consumer Contract

@Pact(consumer = "UserWebClient", provider = "UserService")
public RequestResponsePact createPact(PactDslWithProvider builder) {
    return builder
        .given("User 123 exists")
        .uponReceiving("A request for User 123")
        .path("/users/123")
        .method("GET")
        .willRespondWith()
        .status(200)
        .body(new PactDslJsonBody()
            .stringType("username", "espahi")
            .stringType("email", "enis@example.com"))
        .toPact();
}

Implications for Scalability and Governance

In a microservices environment, the number of interfaces can grow exponentially. Without a standardized approach to communication, the system becomes a “Distributed Monolith” where every change requires cross-team meetings and manual testing.

Enis emphasizes that adopting these automated tools—OpenAPI generators for client libraries and Pact for contract verification—shifts the burden of compatibility from humans to the CI/CD pipeline. This automation allows for “Independent Deployability,” where teams can release updates with the mathematical certainty that they are not breaking downstream consumers.

Conclusion

Enhancing the interaction between API providers and consumers requires a strategic blend of technical standards and human-centric design. By moving toward API-First and Consumer-Driven methodologies, organizations can bridge the gap between intent and implementation. The use of OpenAPI and Pact transforms APIs from fragile connections into robust, documented, and verified contracts. Ultimately, the success of a distributed system depends not just on how well its machines talk, but on how clearly its human creators communicate their expectations.

Links:

PostHeaderIcon [GopherConUK2025] How Just Eat Uses Tooling to Deploy Go Micro-services in Minutes

Lecturer

Ainsley Clark is a Senior Software Engineer at Just Eat, working within the Jet Connect team. He has been with the organisation for just over two years and maintains a strong professional focus on Go. His work centres on the design and operation of the internal microservice development toolkit that underpins large-scale order and menu processing across hundreds of partner integrations.

Abstract

This article describes the evolution and capabilities of GoKit, the internal microservice development toolkit developed by Just Eat’s Jet Connect team. Confronted with the maintenance burden of hundreds of independently forked services, the team replaced a template-based approach with a centralised code-generation and infrastructure-as-code system. The resulting tool scaffolds services, generates event consumers and producers, provisions cloud resources, produces continuous-integration workflows and emits operational metrics with minimal engineer effort. A concrete pizza-order example illustrates how a fully instrumented, event-driven service can be created and extended in minutes rather than days, allowing engineers to concentrate on business logic rather than infrastructure boilerplate.

From Monolith and Templates to a Centralised Toolkit

Jet Connect began life as Flight, an integration platform founded in 2013. Its purpose is to unify order and menu processing across restaurants, groceries and electronics partners so that a single point-of-sale interaction replaces multiple device-specific payloads. Today the platform serves approximately 731 000 partners across seventeen countries and processes hundreds of millions of orders annually. The Jet Connect engineering group itself comprises roughly fifty people and owns more than one hundred Go microservices.

The journey to that estate began with a PHP monolith that became increasingly difficult to change. Integrations were subsequently extracted into TypeScript services that spoke gRPC to the monolith. A GitHub template repository accelerated the creation of new integrations: an engineer could fork the template and obtain environment files, Helm charts and build workflows in minutes. The approach delivered speed in the early days yet exacted a heavy maintenance cost. A bug fix or standardisation change had to be applied manually to every fork. Inconsistent folder structures and coding patterns made context-switching expensive. End-to-end testing was absent, reducing confidence in releases.

The decision was therefore taken to move the entire integration layer to Go and to replace the template model with a purpose-built toolkit. The requirements were exacting: generate or update a repository in seconds; treat OpenAPI documentation as a first-class artefact; allow an engineer to subscribe to events by writing only a handler; hide infrastructure details such as databases, event buses and object storage; auto-generate continuous-integration and continuous-delivery pipelines; guarantee safe production deployment on merge to main; support capability testing across the full service graph; and provide a consistent local development experience with minimal setup. The resulting system is known internally as GoKit.

Scaffolding, Event Handling and Infrastructure as Code

GoKit meets those requirements through a combination of code generation and a declarative service description. The command gokit new <service-name> presents a short interactive questionnaire: does the service consume events, produce events, expose an HTTP server, require a database or object store? On the basis of the answers it scaffolds a consistent folder structure whose most important directories are cmd/app (containing a generated main) and internal (the sole location for custom business logic). A file named service.json acts as the single source of truth for infrastructure; it is consumed by Terraform templates that provision the necessary cloud resources.

A typical event handler receives an HTTP client, an event-bus producer and the incoming event (typed via reflection performed by GoKit). After performing domain work—calling a partner API, writing analytics, storing a payload—the handler emits a successor event. Registration consists of a single method call that associates the handler with a concrete event type. A subsequent gokit update rewrites service.json, updates generated code and refreshes documentation. The resulting README lists every consumed and produced topic, giving any engineer an immediate, accurate overview of the service’s responsibilities without requiring manual documentation effort.

Because the same service.json also drives resource provisioning, adding a DynamoDB table or an S3 bucket is a matter of inserting a few lines of JSON and re-running the update command. Generated interfaces for read/write operations and object-store upload/download encourage dependency injection and make unit testing straightforward. Should the underlying technology later change (for example from DynamoDB to PostgreSQL), only the implementation behind the interface needs to be altered; every consuming service continues to compile and run unchanged. The same mechanism supports vertical and horizontal scaling declarations—minimum replica counts, CPU and memory class—again expressed as simple keys in service.json.

Continuous Integration, Capability Testing and Observability

All continuous-integration workflows are themselves generated by GoKit. A change to a workflow template is made once in the central repository; every service inherits the update the next time gokit update is run. Capability tests spin up the entire relevant service graph under Docker, inject a correlation identifier into every request and event, and assert final outcomes. The approach scales to capability chains that involve ten or twenty intermediate services and provides high confidence before production deployment. Drift detection prevents accidental divergence: if an engineer edits a generated file without running the update command, continuous integration fails the pull request. Version pinning of GoKit itself ensures that services cannot merge while they lag behind a required toolkit version, producing a natural, incremental migration path across the estate.

Operational metrics appear automatically. Grafana dashboards display event rates, HTTP status codes, latency distributions, DynamoDB read/write performance and S3 operation counts without any additional instrumentation code. The same consistency that simplifies development also simplifies observation. When a product request arrives for analytics storage or an order-ready notification endpoint, the engineer adds a handful of lines to service.json or an OpenAPI specification, runs the update command, implements a short handler, and obtains a fully instrumented, tested and documented service ready for review.

Outcomes and Lessons

In the six years of its existence GoKit has allowed the Jet Connect team to update the entire service estate with a single pull request, to maintain uniform folder structures that eliminate costly context switches, and to keep documentation accurate without manual effort. Engineers spend their time on business problems—partner-specific payloads, analytics requirements, notification flows—rather than on Terraform, Helm or continuous-integration boilerplate. The toolkit has been instrumental in scaling the platform to hundreds of millions of orders while keeping the cognitive load on individual developers manageable.

The most transferable lessons are structural rather than technological. A single source of truth for service shape, aggressive code generation of repetitive artefacts, enforcement of consistency through continuous integration, and the deliberate abstraction of infrastructure behind stable interfaces together convert the chronic maintenance burden of a large microservice estate into a manageable, largely automated background process. The result is that complex event-driven workflows can be designed, implemented, tested and deployed in minutes rather than days, freeing engineering capacity for the differentiated work that actually delivers value to partners and customers.
The pizza-service walkthrough, though simplified for presentation, captures the essential rhythm of day-to-day work. An engineer begins with a short questionnaire, receives a fully structured repository, writes a handful of domain-specific lines, runs an update command, and obtains a service that already possesses continuous-integration pipelines, infrastructure declarations, metrics dashboards and capability-test scaffolding. Subsequent product requests—analytics storage, partner callbacks, new event types—are accommodated by small, localised edits rather than by the recreation of entire deployment artefacts. The cognitive load remains focused on the business problem; the mechanical work of packaging, provisioning and observing has been systematically removed from the critical path.

The same pattern scales. Whether the service handles a single partner’s CSV feed or participates in a multi-stage order-reconciliation flow involving a dozen intermediate services, the surrounding machinery remains identical. Consistency of structure, generation of repetitive artefacts and enforcement of hygiene through continuous integration together produce an estate that can grow without a proportional increase in operational friction. That is the practical payoff of the investment in a centralised toolkit.
Looking ahead, the same principles that make GoKit effective inside Just Eat are portable to any organisation that maintains a large collection of similarly structured services. The precise implementation will differ—different cloud providers, different event buses, different continuous-integration systems—but the underlying ideas remain constant: centralise the definition of service shape, generate everything that can be generated, enforce consistency automatically, and keep the engineer’s attention on the business problem. When those ideas are applied with discipline, the cost of creating and operating microservices falls dramatically, and the organisation’s capacity to respond to new partner or product requirements rises correspondingly.
In the end the value of a toolkit such as GoKit is measured less by the number of lines it generates than by the number of decisions it removes from the critical path of feature delivery. Every database, every topic subscription, every continuous-integration step that an engineer no longer has to configure by hand is a unit of attention that can be redirected toward the unique requirements of a partner or a product. When that redirection is systematic and reliable, the organisation as a whole becomes more responsive, more consistent and more capable of sustaining growth without a proportional increase in operational complexity.
The pizza-service narrative also illustrates a secondary benefit that is easy to overlook: the progressive enrichment of operational visibility. Because metrics, dashboards and capability tests are generated rather than hand-crafted, every new service automatically participates in the organisation’s observability and quality regimes. There is no opportunity for a service to be “forgotten” or to ship without the standard instrumentation. Consistency of tooling produces consistency of operational posture, which in turn reduces the cognitive load on both developers and on-call engineers.
Taken together, the practices embodied in GoKit demonstrate that the apparent tension between rapid delivery and long-term maintainability can be resolved by investing in the right abstractions at the platform level. When the platform absorbs the repetitive work of scaffolding, provisioning, testing and observing, individual service teams are free to move quickly without accumulating the structural debt that eventually slows every organisation that scales through pure copy-and-paste. The result is an engineering culture that can sustain high throughput while preserving the coherence and reliability required for a production system that processes hundreds of millions of orders.
The same principles that enabled Jet Connect to move from a maintenance-heavy template model to a coherent, generated estate are available to any organisation facing a similar proliferation of services. The precise technology choices—Terraform versus another infrastructure-as-code system, Kafka versus another event bus, OpenAPI versus another interface description—matter less than the architectural decision to centralise the definition of service shape and to generate the repetitive surrounding machinery. Once that decision is made and enforced, the cost of each additional service falls and the organisation’s ability to respond to new requirements rises. In an environment that processes hundreds of millions of orders, that difference is decisive.
In practical terms the toolkit converts what would otherwise be a multi-day or multi-week effort—creating a repository, wiring continuous integration, declaring infrastructure, adding metrics, writing documentation—into a sequence of minutes. The engineer’s attention remains on the unique aspects of the partner integration or the product feature. Everything else is supplied by generation and enforced by pipeline. That separation of concerns is the essential achievement, and it is the reason the platform can continue to scale without a corresponding explosion in operational overhead.
The experience of Jet Connect demonstrates that the investment in a carefully designed internal platform pays continuing dividends. Each new service inherits the accumulated learning of the entire estate; each improvement to the toolkit propagates automatically; each engineer inherits a consistent, well-instrumented starting point. The result is not merely faster delivery of individual features but a durable increase in the organisation’s capacity to absorb complexity without sacrificing reliability or developer effectiveness.
In short, GoKit is less a code generator than a deliberate architectural intervention that realigns incentives and removes friction from the path of delivery.

Links:

PostHeaderIcon [NDCOslo2024] Choosing The Best AWS Service For Your Website + API – Brandon Minnick

In the sprawling spectrum of cloud solutions, where a plethora of platforms perplex even the seasoned, Brandon Minnick, an AWS architect and mobile maestro, navigates the nebulous nebula of Amazon’s offerings. As a developer advocate with a penchant for demystifying deployment, Brandon dissects the dizzying array of AWS services—Lambda, Elastic Beanstalk, Lightsail, Amplify, S3—distilling their distinct domains to guide builders toward bespoke backends. His exploration, enriched with empirical evaluations, empowers enterprises to align ambition with architecture, balancing cost, celerity, and scalability.

Brandon begins with a confession: his own odyssey, as a mobile maestro thrust into AWS’s vast vault, was overwhelmed by options—acronyms and aliases abounding. His mission: map the maze, matching motives to mechanisms, ensuring websites and APIs ascend with alacrity.

Decoding the Domain: AWS’s Hosting Horizons

AWS’s arsenal is abundant: S3 stores static simplicities, buckets brimming with bits; Amplify augments apps, knitting frontends to functions. Brandon breaks down the basics: Elastic Beanstalk builds bridges, automating infrastructure; Lightsail lightens loads, offering preconfigured planes; Lambda launches lean, serverless scripts scaling seamlessly.

Each excels in its enclave: S3’s simplicity suits static sites, Amplify’s agility aids authenticated apps, Lambda’s litheness loves lightweight logic. Brandon’s benchmark: cost—S3’s cents versus Lambda’s low levies; speed—CloudFront’s celerity; scale—Fargate’s fluidity.

Cost and Celerity: Calculating the Calculus

Price predicates priority: S3’s storage starts at sub-dollar sums, Lambda’s invocations linger at $0.20 per million, Amplify’s adaptability aligns at $0.023 per GB. Brandon’s breakdown: static sites savor S3’s thrift, dynamic domains demand Amplify’s depth—authentication via Cognito, APIs via API Gateway.

Performance pulses: CloudFront’s CDN cuts latency to 300ms, Lambda’s cold starts cede to containers’ constancy. Brandon advises: weigh user whims—300ms matters for markets, less for leisurely loads.

Scalability and Simplicity: Structuring for Surge

Scalability shapes success: Lambda’s limitless leaps, Fargate’s fleet-footed fleets, Beanstalk’s balanced ballast. Brandon illustrates: API Gateway guards gates, throttling torrents; Amplify’s auto-scaling absolves administrative aches.

Simplicity seals the deal: Lightsail’s one-click launches lure lone developers; Amplify’s abstractions attract architects. Brandon’s beacon: start small—S3 for static, scale to Amplify for ambition.

Strategic Selection: Synthesizing Solutions

Brandon’s synthesis: match mission to mechanism—S3 for static starters, Amplify for authenticated ascents, Lambda for lean logic. His counsel: consult AWS’s compendium—getting-started guides, web app wisdom—curated for clarity.

His clarion: choose consciously, calibrating cost, celerity, scalability—AWS’s arsenal awaits.

Links:

PostHeaderIcon [DevoxxFR2025] Boosting Java Application Startup Time: JVM and Framework Optimizations

In the world of modern application deployment, particularly in cloud-native and microservice architectures, fast startup time is a crucial factor impacting scalability, resilience, and cost efficiency. Slow-starting applications can delay deployments, hinder auto-scaling responsiveness, and consume resources unnecessarily. Olivier Bourgain, in his presentation, delved into strategies for significantly accelerating the startup time of Java applications, focusing on optimizations at both the Java Virtual Machine (JVM) level and within popular frameworks like Spring Boot. He explored techniques ranging from garbage collection tuning to leveraging emerging technologies like OpenJDK’s Project Leyden and Spring AOT (Ahead-of-Time Compilation) to make Java applications lighter, faster, and more efficient from the moment they start.

The Importance of Fast Startup

Olivier began by explaining why fast startup time matters in modern environments. In microservices architectures, applications are frequently started and stopped as part of scaling events, deployments, or rolling updates. A slow startup adds to the time it takes to scale up to handle increased load, potentially leading to performance degradation or service unavailability. In serverless or function-as-a-service environments, cold starts (the time it takes for an idle instance to become ready) are directly impacted by application startup time, affecting latency and user experience. Faster startup also improves developer productivity by reducing the waiting time during local development and testing cycles. Olivier emphasized that optimizing startup time is no longer just a minor optimization but a fundamental requirement for efficient cloud-native deployments.

JVM and Garbage Collection Optimizations

Optimizing the JVM configuration and understanding garbage collection behavior are foundational steps in improving Java application startup. Olivier discussed how different garbage collectors (like G1, Parallel, or ZGC) can impact startup time and memory usage. Tuning JVM arguments related to heap size, garbage collection pauses, and just-in-time (JIT) compilation tiers can influence how quickly the application becomes responsive. While JIT compilation is crucial for long-term performance, it can introduce startup overhead as the JVM analyzes and optimizes code during initial execution. Techniques like Class Data Sharing (CDS) were mentioned as a way to reduce startup time by sharing pre-processed class metadata between multiple JVM instances. Olivier provided practical tips and configurations for optimizing JVM settings specifically for faster startup, balancing it with overall application performance.

Framework Optimizations: Spring Boot and Beyond

Popular frameworks like Spring Boot, while providing immense productivity benefits, can sometimes contribute to longer startup times due to their extensive features and reliance on reflection and classpath scanning during initialization. Olivier explored strategies within the Spring ecosystem and other frameworks to mitigate this. He highlighted Spring AOT (Ahead-of-Time Compilation) as a transformative technology that analyzes the application at build time and generates optimized code and configuration, reducing the work the JVM needs to do at runtime. This can significantly decrease startup time and memory footprint, making Spring Boot applications more suitable for resource-constrained environments and serverless deployments. Project Leyden in OpenJDK, aiming to enable static images and further AOT compilation for Java, was also discussed as a future direction for improving startup performance at the language level. Olivier demonstrated how applying these framework-specific optimizations and leveraging AOT compilation can have a dramatic impact on the startup speed of Java applications, making them competitive with applications written in languages traditionally known for faster startup.

Links:

PostHeaderIcon [DevoxxGR2025] Email Re-Platforming Case Study

George Gkogkolis from Travelite Group shared a 15-minute case study at Devoxx Greece 2025 on re-platforming to process 1 million emails per hour.

The Challenge

Travelite Group, a global OTA handling flight tickets in 75 countries, processes 350,000 emails daily, expected to hit 2 million. Previously, a SaaS ticketing system struggled with growing traffic, poor licensing, and subpar user experience. Sharding the system led to complex agent logins and multiplexing issues with the booking engine. Market research revealed no viable alternatives, as vendors’ licensing models couldn’t handle the scale, prompting an in-house solution.

The New Platform

The team built a cloud-native, microservices-based platform within a year, going live in December 2024. It features a receiving app, a React-based web UI with Mantine Dev, a Spring Boot backend, and Amazon DocumentDB, integrated with Amazon SES and S3. Emails land in a Postfix server, are stored in S3, and processed via EventBridge and SQS. Data migration was critical, moving terabytes of EML files and databases in under two months, achieving a peak throughput of 1 million emails per hour by scaling to 50 receiver instances.

Lessons Learned

Starting with migration would have eased performance optimization, as synthetic data didn’t match production scale. Cloud-native deployment simplified scaling, and a backward-compatible API eased integration. Open standards (EML, Open API) ensured reliability. Future plans include AI and LLM enhancements by 2025, automating domain allocation for scalability.

Links

PostHeaderIcon [DevoxxGR2025] Orchestration vs. Choreography: Balancing Control and Flexibility in Microservices

At Devoxx Greece 2025, Laila Bougria, representing Particular Software, delivered an insightful presentation on the nuances of orchestration and choreography in microservice architectures. Leveraging her extensive banking industry experience, Laila provided a practical framework to navigate the trade-offs of these coordination strategies, using real-world scenarios to guide developers toward informed system design choices.

The Essence of Microservice Interactions

Laila opened with a relatable story about navigating the mortgage process, underscoring the complexity of interservice communication in microservices. She explained that while individual services are streamlined, the real challenge lies in orchestrating their interactions to deliver business value. Orchestration employs a centralized component to direct workflows, maintaining state and issuing commands, much like a conductor guiding a symphony. Choreography, by contrast, embraces an event-driven model where services operate autonomously, reacting to events with distributed state management. Through a loan broker example, Laila illustrated how orchestration simplifies processes like credit checks and offer ranking by centralizing control, yet risks creating dependencies that can halt workflows if services fail. Choreography, facilitated by an event bus, enhances autonomy but complicates tracking the overall process, potentially obscuring system behavior.

Navigating Coupling and Resilience

Delving into the mechanics, Laila highlighted the distinct coupling profiles of each approach. Orchestration often leads to efferent coupling, with the central component relying on multiple downstream services, necessitating resilience mechanisms like retries or circuit breakers to mitigate failures. For instance, if a credit scoring service is unavailable, the orchestrator must handle retries or fallback strategies. Choreography, however, increases afferent coupling through event subscriptions, which can introduce bidirectional dependencies when addressing business failures, such as reversing a loan if a property deal collapses. Laila stressed the importance of understanding coupling types—temporal, contract, and control—to make strategic decisions. Asynchronous communication in orchestration reduces temporal coupling, while choreography’s event-driven nature supports scalability but challenges visibility, as seen in her banking workflow example where emergent behavior obscured process clarity.

Addressing Business Failures and Workflow Evolution

Laila emphasized the critical role of managing business failures, or compensating flows, where actions must be undone due to unforeseen events, like a failed property transaction requiring the reversal of interest provisions or direct debits. Orchestration excels here, leveraging existing service connections to streamline reversals. In contrast, choreography demands additional event subscriptions, risking complex bidirectional coupling, as demonstrated when adding a background check to a loan process introduced order dependencies. Laila introduced the concept of “passive-aggressive publishers,” where services implicitly rely on others to act on events, akin to expecting a partner to address a chaotic kitchen without direct communication. She advocated for explicit command-driven interactions to clarify dependencies, ensuring system robustness. Additionally, Laila addressed workflow evolution, noting that orchestration simplifies modifications by centralizing changes, while choreography requires careful management to avoid disrupting event-driven flows.

A Strategic Decision Framework

Concluding her talk, Laila offered a decision-making framework anchored in five questions: the nature of communication (synchronous or asynchronous), the complexity of prerequisites, the extent of compensating flows, the likelihood of domain changes, and the need for centralized responsibility. Orchestration suits critical workflows with frequent changes or complex dependencies, such as banking processes requiring clear state visibility. Choreography is ideal for stable domains with minimal prerequisites, like retail order systems. By segmenting workflows into sub-processes, developers can apply the appropriate pattern strategically, blending both approaches for optimal outcomes. Laila’s banking-inspired insights provide a practical guide for architects to craft systems that balance control, flexibility, and maintainability.

Links:

PostHeaderIcon [NDCOslo2024] Kafka for .NET Developers – Ian Cooper

In the torrent of event-driven ecosystems, where streams supplant silos and resilience reigns, Ian Cooper, a polyglot architect and Brighter’s steward, demystifies Kafka for .NET artisans. As London’s #ldnug founder and a messaging maven, Ian unravels Kafka’s enigma—records, offsets, SerDes, schemas—from novice nods to nuanced integrations. His hour, a whirlwind of wisdom and wireframes, equips ensembles to embed Kafka as backbone, blending brokers with .NET’s breadth for robust, reactive realms.

Ian immerses immediately: Kafka, a distributed commit log, chronicles changes for consumption, contrasting queues’ ephemera. Born from LinkedIn’s logging ledger in 2011, it scaled to streams, spawning Connect for conduits and Flink for flows. Ian’s inflection: Kafka as nervous system, not notification nook—durable, disorderly, decentralized.

Unpacking the Pipeline: Kafka’s Primal Primitives

Kafka’s corpus: topics as ledgers, partitioned for parallelism, replicated for redundancy. Producers pen records—key-value payloads with headers—SerDes serializing strings or structs. Consumers cull via offsets, groups coordinating coordination, enabling elastic elasticity.

Ian illuminates inroads: Confluent’s Cloud for coddling, self-hosted for sovereignty. .NET’s ingress: Confluent.Kafka NuGet, crafting IProducer for publishes, IConsumer for pulls. His handler: await producer.ProduceAsync(topic, new Message {Key = key, Value = serialized}).

Schemas safeguard: registries register Avro or Protobuf, embedding IDs for evolution. Ian’s caveat: magic bytes mandate manual marshaling in .NET, yet compatibility curtails chaos.

Forging Flows: From Fundamentals to Flink Frontiers

Fundamentals flourish: idempotent producers preclude duplicates, transactions tether topics. Ian’s .NET nuance: transactions via BeginTransaction, committing confluences. Exactly-once semantics, once Java’s jewel, beckon .NET via Kafka Streams’ kin.

Connect catalyzes: sink sources to SQL, sources streams from files—redpanda’s kin for Kafka-less kinship. Flink forges further: stream processors paralleling data dances, yet .NET’s niche narrows to basics.

Ian’s interlude: brighter bridges, abstracting brokers for seamless swaps—Rabbit to Kafka—sans syntactic shifts.

Safeguarding Streams: Resilience and Realms

Resilience roots in replicas: in-sync sets (ISR) insure idempotence, unclean leader elections avert anarchy. Ian’s imperative: tune retention—time or tally—for traceability, not torrent.

His horizon: Kafka as canvas for CQRS, where commands commit, queries query—event sourcing’s engine.

Links:

PostHeaderIcon Efficient Inter-Service Communication with Feign and Spring Cloud in Multi-Instance Microservices

In a world where systems are becoming increasingly distributed and cloud-native, microservices have emerged as the de facto architecture. But as we scale
microservices horizontally—running multiple instances for each service—one of the biggest challenges becomes inter-service communication.

How do we ensure that our services talk to each other reliably, efficiently, and in a way that’s resilient to failures?

Welcome to the world of Feign and Spring Cloud.


The Challenge: Multi-Instance Microservices

Imagine you have a user-service that needs to talk to an order-service, and your order-service runs 5 instances behind a
service registry like Eureka. Hardcoding URLs? That’s brittle. Manual load balancing? Not scalable.

You need:

  • Service discovery to dynamically resolve where to send the request
  • Load balancing across instances
  • Resilience for timeouts, retries, and fallbacks
  • Clean, maintainable code that developers love

The Solution: Feign + Spring Cloud

OpenFeign is a declarative web client. Think of it as a smart HTTP client where you only define interfaces — no more boilerplate REST calls.

When combined with Spring Cloud, Feign becomes a first-class citizen in a dynamic, scalable microservices ecosystem.

✅ Features at a Glance:

  • Declarative REST client
  • Automatic service discovery (Eureka, Consul)
  • Client-side load balancing (Spring Cloud LoadBalancer)
  • Integration with Resilience4j for circuit breaking
  • Easy integration with Spring Boot config and observability tools

Step-by-Step Setup

1. Add Dependencies

[xml][/xml]

If using Eureka:

[xml][/xml]


2. Enable Feign Clients

In your main Spring Boot application class:

[java]@SpringBootApplication
@EnableFeignClients
public <span>class <span>UserServiceApplication { … }
[/java]


3. Define Your Feign Interface

[java]
@FeignClient(name = "order-service")
public interface OrderClient { @GetMapping("/orders/{id}")
OrderDTO getOrder(@PathVariable("id") Long id); }
[/java]

Spring will automatically:

  • Register this as a bean
  • Resolve order-service from Eureka
  • Load-balance across all its instances

4. Add Resilience with Fallbacks

You can configure a fallback to handle failures gracefully:

[java]

@FeignClient(name = "order-service", fallback = OrderClientFallback.class)
public interface OrderClient {
@GetMapping("/orders/{id}") OrderDTO getOrder(@PathVariable Long id);
}[/java]

The fallback:

[java]

@Component
public class OrderClientFallback implements OrderClient {
@Override public OrderDTO getOrder(Long id) {
return new OrderDTO(id, "Fallback Order", LocalDate.now());
}
}[/java]


⚙️ Configuration Tweaks

Customize Feign timeouts in application.yml:

[yml]

feign:

    client:

       config:

           default:

                connectTimeout:3000

                readTimeout:500

[/yml]

Enable retry:

[xml]
feign:
client:
config:
default:
retryer:
maxAttempts: 3
period: 1000
maxPeriod: 2000
[/xml]


What Happens Behind the Scenes?

When user-service calls order-service:

  1. Spring Cloud uses Eureka to resolve all instances of order-service.
  2. Spring Cloud LoadBalancer picks an instance using round-robin (or your chosen strategy).
  3. Feign sends the HTTP request to that instance.
  4. If it fails, Resilience4j (or your fallback) handles it gracefully.

Observability & Debugging

Use Spring Boot Actuator to expose Feign metrics:

[xml]

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency[/xml]

And tools like Spring Cloud Sleuth + Zipkin for distributed tracing across Feign calls.


Beyond the Basics

To go even further:

  • Integrate with Spring Cloud Gateway for API routing and external access.
  • Use Spring Cloud Config Server to centralize configuration across environments.
  • Secure Feign calls with OAuth2 via Spring Security and OpenID Connect.

✨ Final Thoughts

Using Feign with Spring Cloud transforms service-to-service communication from a tedious, error-prone task into a clean, scalable, and cloud-native solution.
Whether you’re scaling services across zones or deploying in Kubernetes, Feign ensures your services communicate intelligently and resiliently.