[KotlinConf2019] Desktop Development with TornadoFX: Kotlinizing JavaFX with Liz Keogh
JavaFX, the successor to Swing for creating rich client applications in Java, offers a modern approach to desktop UI development with a cleaner separation of function and style. However, working directly with JavaFX can sometimes involve verbosity and untyped stylesheets. Liz Keogh, a renowned Lean and Agile consultant and a core member of the BDD community, presented a compelling alternative at KotlinConf 2019: TornadoFX. Her talk explored how TornadoFX, a Kotlin wrapper around JavaFX, simplifies desktop development with type-safe builders, stylesheets, and the syntactic sugar Kotlin developers appreciate. Liz Keogh’s consultancy work can often be found via lunivore.com.
TornadoFX aims to make JavaFX development more idiomatic and enjoyable for Kotlin developers. It leverages Kotlin’s powerful features to reduce boilerplate and introduce modern development patterns like dependency-injected MVC/MVP. The official website for the framework is tornadofx.io.
Simplifying JavaFX with Kotlin’s Elegance
Liz Keogh’s session highlighted how TornadoFX enhances the JavaFX experience. Key advantages include:
* Type-Safe Builders: Instead of manually instantiating and configuring UI components in JavaFX, TornadoFX provides type-safe builders. This allows for a more declarative and concise way to define UI layouts, reducing the chance of runtime errors and improving code readability.
* Type-Safe Stylesheets: JavaFX typically uses CSS for styling, which is not type-safe and can be cumbersome. TornadoFX introduces type-safe CSS, allowing styles to be defined directly in Kotlin code with autocompletion and compile-time checking. This makes styling more robust and easier to manage.
* Dependency Injection and Architectural Patterns: TornadoFX incorporates support for architectural patterns like Model-View-Controller (MVC) and Model-View-Presenter (MVP) with built-in dependency injection. This helps in structuring desktop applications in a clean, maintainable, and testable way.
* Kotlin’s Syntactic Sugar: The framework makes full use of Kotlin’s features, such as extension functions, lambdas, and DSL capabilities, to create a fluent and expressive API for building UIs.
Liz demonstrated these features through practical examples, showing how quickly developers can create sophisticated desktop applications with significantly less code compared to plain JavaFX.
Practical Application: Building a Desktop Game
To illustrate the power and ease of use of TornadoFX, Liz Keogh built a desktop version of the game “Don’t Get Mad!” (a variant of Ludo/Pachisi) live during her presentation. This hands-on approach allowed attendees to see TornadoFX in action, from setting up the project to building the UI, implementing game logic, and handling user interactions.
She showcased how to:
* Define views and components using TornadoFX’s builders.
* Apply styles using type-safe CSS.
* Manage application state and events.
* Integrate game logic written in Kotlin.
While focusing on TornadoFX, Liz also touched upon broader software development principles, such as the importance of automated testing. She candidly mentioned her preference for unit tests and the need for more in her demo project due to deadline constraints, reminding attendees about the test pyramid. This practical demonstration of building a game provided a tangible example of what’s possible with TornadoFX and how it can accelerate desktop development.
TornadoFX and the Kotlin Ecosystem
Liz Keogh’s presentation positioned TornadoFX as a valuable addition to the Kotlin ecosystem, particularly for developers looking to build desktop applications. By providing a more Kotlin-idiomatic layer over JavaFX, TornadoFX lowers the barrier to entry for desktop development and makes it a more attractive option for the Kotlin community.
She also mentioned another personal project, “K Golf” (Kotlin Game of Life), a JavaFX application she uses for teaching Kotlin, hinting at her passion for both Kotlin and creating engaging learning experiences. Her talk inspired many Kotlin developers to explore TornadoFX for their desktop application needs, showcasing it as a productive and enjoyable way to leverage their Kotlin skills in a new domain. The session underscored the theme of Kotlin’s versatility, extending its reach effectively into desktop development.
Links:
[DevoxxPL2019] Semantic Event Sourcing: A Case Study of Transitioning from CRUD to Log-Based State Management
Lecturer
Neil Boddy is an engineer at Goldman Sachs, working in master data management. His expertise focuses on scalable data architectures, including shifts from relational CRUD to NoSQL and event-sourcing techniques for handling complex, high-volume financial data.
Abstract
This article delves into the transition from traditional CRUD operations in relational databases to log-based state management using event sourcing in a master data management context at Goldman Sachs. It analyzes the motivations, engineering challenges, and solutions for managing large-scale, fragmented data flows. Concepts like immutable events, semantic metadata, and sharded clusters are examined, with emphasis on resilience, recovery, and standardized access. Implications for fault tolerance and scalability in data-intensive applications are explored through practical observations.
Challenges in Traditional CRUD-Based Master Data Management
Master data management (MDM) involves processes, tools, and policies to create and maintain critical data shared across an organization, such as financial instruments (bonds, equities) or static references (currencies, countries). Neil describes Goldman Sachs’ environment with hundreds of heterogeneous data flows, schemas, and databases processing millions of structurally complex records daily—e.g., a bond with 300 attributes expanding to thousands at runtime due to relationships.
Traditional CRUD persists only the latest state, leading to issues: schema proliferation, domain-specific expertise needs, and poor scalability for large set operations (e.g., universe comparisons). Sequential processing fragility exacerbates problems; out-of-order batches corrupt states, requiring forensic analysis, migrations, and business disruptions. Human errors, buggy code, or upstream issues cascade, with recovery being error-prone and stressful.
Neil contrasts this with accounting’s compensatory transactions, preserving audit trails—paralleling event sourcing’s immutable events to avoid irreversible damage.
Principles of Event Sourcing and Log-Based Management
Event sourcing reconstructs state from sequenced events, inspired by domain-driven design. Unlike CRUD’s mutable updates, events are append-only, capturing changes immutably. Neil illustrates with a shopping cart: CRUD shows final quantities, but events reveal full history (additions, removals), enabling replays and audits.
In MDM, data arrives fragmented (e.g., car analogy: engine, chassis batches). Aggregates ensure logical consistency, atomic storage, and intuitive structures. Using MongoDB as a document store and log, events are stored in collections, supporting sharding for scalability.
Two-dimensional time—business (effective date) and system (processing timestamp)—enables point-in-time queries. For Wednesday’s data viewed Friday, include all events; rewind system time for as-at views.
Technical Implementation with MongoDB
Goldman Sachs employs a pipe-and-filters architecture: staging, transformation, resolution, persistence. MongoDB’s document model accommodates multi-structured data without custom schemas, embracing event sourcing semantics (full universes, deltas, corrections).
Dual logs: one for business data (minimal indexes, large sets), another for semantic metadata (rich indexes, small footprint). Metadata captures batch types, times, and segments, facilitating precise queries.
Sharded clusters partition logs, enabling concurrent queries. Process: query metadata log for pointers, then concurrently fetch from business log across nodes. Deduplicate by grouping identities, selecting latest observing 2D time, and dropping tombstones.
Projections support use cases: full universes, changes across times/segments, replays with deduplication, historic analyses.
Code sample for MongoDB aggregation (simplified projection):
db.businessLog.aggregate([
{ $match: { identity: "100", businessTime: { $lte: ISODate("2023-03-03") } } },
{ $sort: { systemTime: -1 } },
{ $group: { _id: "$identity", latest: { $first: "$$ROOT" } } },
{ $match: { "latest.tombstone": { $ne: true } } }
]);
This fetches latest non-tombstoned record for an identity up to a business date.
Resilience, Recovery, and Performance Gains
Log-based approaches enhance fault tolerance: stale data redefines points without corruption; rogue data exclusion is straightforward. Parallel recovery reduces times (e.g., 30 hours to 4), supporting out-of-order processing.
Low-friction persistence eases backups, indexing, and maintenance. Concurrency spreads read/write loads; rich metadata enables hygiene controls (e.g., preventing base data on processed days).
Standardization reduces variation, improving scalability over CRUD’s custom solutions.
Broader Implications and Observations
This shift yields richer data utility for computations (deltas, reconciliations) without data copying. Retired records are handled via log points, avoiding deletions. While new concepts and tech require learning, benefits include standardized access, tolerance to errors, and high availability via redundancy.
Neil notes applicability beyond MDM to batch-oriented, large-scale state management, though not universal. In conclusion, semantic event sourcing fortifies systems against fragility, promoting scalable, recoverable architectures in data-centric domains.
Links:
[SpringIO2019] Cloud Native Spring Boot Admin by Johannes Edmeier
At Spring I/O 2019 in Barcelona, Johannes Edmeier, a seasoned developer from Germany, captivated attendees with his deep dive into managing Spring Boot applications in Kubernetes environments using Spring Boot Admin. As the maintainer of this open-source project, Johannes shared practical insights into integrating Spring Boot Admin with Kubernetes via the Spring Cloud Kubernetes project. His session illuminated how developers can gain operational visibility and control without altering application code, making it a must-know tool for cloud-native ecosystems. This post explores Johannes’ approach, highlighting its relevance for modern DevOps.
Understanding Spring Boot Admin
Spring Boot Admin, a four-and-a-half-year-old project boasting over 17,000 GitHub stars, is an Apache-licensed tool designed to monitor and manage Spring Boot applications. Johannes, employed by ConSol, a German consultancy, dedicates 20% of his work time—and significant personal hours—to its development. The tool provides a user-friendly interface to visualize metrics, logs, and runtime configurations, addressing the limitations of basic monitoring solutions like plain metrics or logs. For Kubernetes-deployed applications, it leverages Spring Boot Actuator endpoints to deliver comprehensive insights without requiring code changes or new container images.
The challenge in cloud-native environments lies in achieving visibility into distributed systems. Johannes emphasized that Kubernetes, a common denominator across cloud vendors, demands robust monitoring tools. Spring Boot Admin meets this need by integrating with Spring Cloud Kubernetes, enabling service discovery and dynamic updates as services scale or fail. This synergy ensures developers can manage applications seamlessly, even in complex, dynamic clusters.
Setting Up Spring Boot Admin on Kubernetes
Configuring Spring Boot Admin for Kubernetes is straightforward, as Johannes demonstrated. Developers start by including the Spring Boot Admin starter server dependency, which bundles the UI and REST endpoints, and the Spring Cloud Kubernetes starter for service discovery. These dependencies, managed via Spring Cloud BOM, simplify setup. Johannes highlighted the importance of enabling the admin server, discovery client, and scheduling annotations in the application class to ensure health checks and service updates function correctly. A common pitfall, recently addressed in the documentation, is forgetting to enable scheduling, which prevents dynamic service updates.
For Kubernetes deployment, Johannes pre-built a Docker image and configured a service account with role-based access control (RBAC) to read pod, service, and endpoint data. This minimal RBAC setup avoids unnecessary permissions, enhancing security. An ingress and service complete the deployment, allowing access to the Spring Boot Admin UI. Johannes showcased a wallboard view, ideal for team dashboards, and demonstrated real-time monitoring by simulating a service failure, which triggered a yellow “restricted” status and subsequent recovery as Kubernetes rescheduled the pod.
Enhancing Monitoring with Actuator Endpoints
Spring Boot Admin’s power lies in its integration with Spring Boot Actuator, which exposes endpoints like health, info, metrics, and more. By default, only health and info endpoints are exposed, but Johannes showed how to expose all endpoints using a Kubernetes environment variable (management.endpoints.web.exposure.include=*). This unlocks detailed views for metrics, environment properties, beans, and scheduled tasks. For instance, the health endpoint provides granular details when set to “always” show details, revealing custom health indicators like database connectivity.
Johannes also highlighted advanced features, such as rendering Swagger UI links via the info endpoint’s properties, simplifying access to API documentation. For security, he recommended isolating Actuator endpoints on a separate management port (e.g., 9080) to prevent public exposure via the main ingress. Spring Cloud Kubernetes facilitates this by allowing developers to specify the management port for discovery, ensuring Spring Boot Admin accesses Actuator endpoints securely while keeping them hidden from external traffic.
Customization and Security Considerations
Spring Boot Admin excels in customization, catering to specific monitoring needs. Johannes demonstrated how to add top-level links to external tools like Grafana or Kibana, or embed them as iframes, reducing the need to memorize URLs. For advanced use cases, developers can create custom views using Vue.js, as Johannes did to toggle application status (e.g., setting a service to “out of service”). This flexibility extends to notifications, supporting Slack, Microsoft Teams, and email via simple configurations, with a test SMTP server like MailHog for demos.
Security is a critical concern, as Spring Boot Admin proxies requests to Actuator endpoints. Johannes cautioned against exposing the admin server publicly, citing an unsecured instance found via Google. He outlined three security approaches: no authentication (not recommended), session-based authentication with cookies, or OAuth2 with token forwarding, where the target application validates access. A service account handles background health checks, ensuring minimal permissions. For Keycloak integration, Johannes referenced a blog post by his colleague Tomas, showcasing Spring Boot Admin’s compatibility with modern security frameworks.
Runtime Management and Future Enhancements
Spring Boot Admin empowers runtime management, a standout feature Johannes showcased. The loggers endpoint allows dynamic adjustment of logging levels, with a forthcoming feature to set levels across all instances simultaneously. Other endpoints, like Jolokia for JMX interaction, enable runtime reconfiguration but require caution due to their power. Heap and thread dump endpoints aid debugging but risk exposing sensitive data or overwhelming resources. Johannes also previewed upcoming features, like minimum instance checks, enhancing Spring Boot Admin’s robustness in production.
For Johannes, Spring Boot Admin is more than a monitoring tool—it’s a platform for operational excellence. By integrating seamlessly with Kubernetes and Spring Boot Actuator, it addresses the complexities of cloud-native applications, empowering developers to focus on delivering value. His session at Spring I/O 2019 underscores its indispensable role in modern software ecosystems.
Links:
[DevoxxPL2019] Reactive for the Impatient: A Gentle Introduction to Reactive Programming and Systems
Lecturer
Mary Grygleski serves as a developer advocate at IBM, based in Chicago. She organizes the Chicago Java Users Group (CJUG) and leads IBM-sponsored meetups on topics like reactive systems and cloud technologies. Her background includes promoting community engagement and advancing Java-based reactive frameworks.
Abstract
This article provides an in-depth exploration of reactive programming and systems, emphasizing their emergence to address modern computing demands for responsiveness and scalability. It delineates core principles from the Reactive Manifesto, differentiates reactive paradigms, and surveys key Java libraries: RxJava, Spring Reactor, Akka, and Vert.x. Analytical insights into patterns, methodologies, and real-world applications underscore the significance of asynchronicity, elasticity, and fault tolerance in building impatient-user-friendly systems.
Emergence and Principles of Reactive Systems
The surge in reactive methodologies arises from hardware advancements, such as multi-core CPUs and cloud virtualization, coupled with escalating user expectations for instantaneous responses. Mary traces reactive roots to the 1980s actor model in Erlang for real-time telecommunications, now adapted to handle proliferating devices and concurrent requests. Human impatience drives this evolution, mirroring family dynamics where multiple demands require asynchronous handling.
The Reactive Manifesto, led by Lightbend (creators of Akka), outlines four pillars: responsiveness, elasticity, resiliency, and message-driven architecture. Responsiveness ensures timely replies, even in failures, forming the usability foundation. Elasticity scales resources dynamically under varying loads, maintaining throughput. Resiliency employs replication and isolation for fault containment, preventing systemic collapses. Message-driven mechanics enable the others, facilitating asynchronous, non-blocking communication akin to event-driven systems but with addressed destinations.
Mary clarifies distinctions: reactive programming propagates changes via event streams, functional reactive programming advances via execution threads, and reactive systems orchestrate isolated components cohesively. Event-driven emits unaddressed events for observers, while message-driven specifies recipients, enhancing coordination.
Patterns and Terminologies in Reactive Programming
Reactive programming revolves around responding to external stimuli through event propagation. Streams represent sequential data elements, fundamental to reactivity. Observables emit event streams, observed by subscribers, drawing from design patterns like observer, composite, and iterator.
Using marble diagrams, Mary illustrates streams: empty timelines await events, marbles denote data, vertical lines signal completion. Backpressure management prevents overwhelming consumers. Reactive extensions (Rx) standardize these, with RxJava implementing them in Java.
A noodle shop analogy piques interest: ordering mimics reactive flows, where requests (events) trigger preparations (responses) asynchronously, handling multiple patrons without blocking.
Survey of Java Reactive Libraries: RxJava and Spring Reactor
RxJava, Netflix’s 2013 port of Microsoft’s Reactive Extensions, supports Java 6+ with backpressure in version 2 (2016). It enables declarative, functional-style programming for asynchronous data streams.
Code sample for a simple observable:
import io.reactivex.Flowable;
public class HelloWorld {
public static void main(String[] args) {
Flowable.fromArray(args).subscribe(System.out::println);
}
}
This pipelines arguments into a flowable, subscribing for output.
Spring Reactor, from Pivotal, leverages Java 8 streams for cleaner APIs, fully supporting reactive streams. It integrates with Kafka, Netty, and others.
Comparative example:
// Traditional Spring MVC (blocking)
@GetMapping("/products")
public List<Product> getProducts() {
System.out.println("Traditional way started");
List<Product> products = productService.getProducts();
System.out.println("Traditional way completed");
return products;
}
// Reactive WebFlux (non-blocking)
@GetMapping(value = "/product-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Product> getProductStream() {
System.out.println("Reactive way using Flux started");
Flux<Product> productFlux = productService.getProductStream();
System.out.println("Reactive way using Flux completed");
return productFlux;
}
The reactive version returns a Flux (ticket) immediately, processing asynchronously.
RxJava partially supports reactive streams; Reactor fully, with Reactor favoring Java 8+ for elegance.
Advanced Frameworks: Akka and Vert.x
Akka, from Lightbend, embodies the actor model for event-driven, location-transparent systems. Actors handle functions isolately, with supervisors managing failures for resiliency.
Java Akka hello world:
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
public class HelloWorld extends AbstractActor {
@Override
public void preStart() {
final ActorRef greeter = getContext().actorOf(Props.create(Greeter.class), "greeter");
greeter.tell(Greeter.Msg.GREET, getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(Greeter.Msg.DONE, msg -> getContext().stop(getSelf()))
.build();
}
}
Scala variant condenses this, leveraging functional conciseness.
Vert.x, from Eclipse, is polyglot, supporting mixed languages. Verticles (actor-like) execute on events, with an event bus for communication.
Vert.x HTTP server:
import io.vertx.core.Vertx;
public class HelloWorldServer {
public static void main(String[] args) {
Vertx.vertx().createHttpServer()
.requestHandler(req -> req.response().end("Hello World"))
.listen(8080);
}
}
Vert.x’s lightweight, non-container-bound nature suits diverse integrations.
Implications for Modern Software Development
Reactive approaches mitigate blocking I/O pitfalls, though database engines lag in full reactivity (e.g., R2DBC offers non-blocking connectivity, but underlying engines remain blocking). Mary advocates community participation, like her reactive meetup group, to foster learning.
In conclusion, reactive paradigms empower scalable, responsive systems, aligning software with hardware and user demands. Frameworks like RxJava, Reactor, Akka, and Vert.x provide tools for implementation, promising flexible, fault-tolerant architectures.
Links:
Kotlin Native Concurrency Explained by Kevin Galligan
Navigating Kotlin/Native’s Concurrency Model
At KotlinConf 2019 in Copenhagen, Kevin Galligan, a partner at Touchlab with over 20 years of software development experience, delivered a 39-minute talk on Kotlin/Native’s concurrency model. Kevin Galligan explored the restrictive yet logical rules governing state and concurrency in Kotlin/Native, addressing their controversy among JVM and mobile developers. He explained the model’s mechanics, its rationale, and best practices for multiplatform development. This post covers four key themes: the core rules of Kotlin/Native concurrency, the role of workers, the impact of freezing state, and the introduction of multi-threaded coroutines.
Core Rules of Kotlin/Native Concurrency
Kevin Galligan began by outlining Kotlin/Native’s two fundamental concurrency rules: mutable state is confined to a single thread, and immutable state can be shared across multiple threads. These rules, known as thread confinement, mirror mobile development practices where UI updates are restricted to the main thread. In Kotlin/Native, the runtime enforces these constraints, preventing mutable state changes from background threads to avoid race conditions. Kevin emphasized that while these rules feel restrictive compared to the JVM’s shared-memory model, they align with modern platforms like Go and Rust, which also limit unrestricted shared state.
The rationale behind this model, as Kevin explained, is to reduce concurrency errors by design. Unlike the JVM, which trusts developers to manage synchronization, Kotlin/Native’s runtime verifies state access at runtime, crashing if rules are violated. This strictness, though initially frustrating, encourages intentional state management. Kevin noted that after a year of working with Kotlin/Native, he found the model simple and effective, provided developers embrace its constraints rather than fight them.
Workers as Concurrency Primitives
A central concept in Kevin’s talk was the Worker, a Kotlin/Native concurrency queue similar to Java’s ExecutorService or Android’s Handler and Looper. Workers manage a job queue processed by a private thread, ensuring thread confinement. Kevin illustrated how a Worker executes tasks via the execute function, which takes a producer function to verify state transfer between threads. The execute function supports safe and unsafe transfer modes, with Kevin strongly advising against the unsafe mode due to its bypassing of state checks.
Using a code example, Kevin demonstrated passing a data class to a Worker. The runtime freezes the data—making it immutable—to comply with concurrency rules, preventing illegal state transfers. He highlighted that while Worker is a core primitive, developers rarely use it directly, as higher-level abstractions like coroutines are preferred. However, understanding Worker is crucial for grasping Kotlin/Native’s concurrency mechanics, especially when debugging state-related errors like IllegalStateTransfer.
Freezing State and Its Implications
Kevin Galligan delved into the concept of freezing, a runtime mechanism that designates objects as immutable for safe sharing across threads. Freezing is a one-way operation, recursively applying to an object and its references, with no unfreeze option. This ensures thread safety but introduces challenges, as frozen objects cannot be mutated, leading to InvalidMutabilityException errors if attempted.
In a practical example, Kevin showed how capturing mutable state in a background task can inadvertently freeze an entire object graph, causing runtime failures. He introduced tools like ensureNeverFrozen to debug unintended freezing and stressed intentional mutability—keeping mutable state local to one thread and transforming data into frozen copies for sharing. Kevin also discussed Atomic types, which allow limited mutation of frozen state, but cautioned against overusing them due to performance and memory issues. His experience at Touchlab revealed early missteps with global state and Atomics, leading to a shift toward confined state models.
Multi-Threaded Coroutines and Future Directions
A significant update in Kevin’s talk was the introduction of multi-threaded coroutines, enabled by a draft pull request in 2019. Previously, Kotlin/Native coroutines were single-threaded, limiting concurrency and stunting library development. The new model allows coroutines to switch threads using dispatchers, with data passed between threads frozen to maintain strict mode. Kevin demonstrated replacing a custom background function with a coroutine-based approach, simplifying concurrency while adhering to state rules.
This development clarified the longevity of strict mode, countering speculation about a relaxed mode that would mimic JVM-style shared memory. Kevin noted that multi-threaded coroutines unblocked library development, citing projects like AtomicFu and SQLDelight. He also highlighted Touchlab’s Droidcon app, which adopted multi-threaded coroutines for production, showcasing their practical viability. Looking forward, Kevin anticipated increased community adoption and library growth in 2020, urging developers to explore the model despite its learning curve.
Conclusion
Kevin Galligan’s KotlinConf 2019 talk demystifies Kotlin/Native’s concurrency model, offering a clear path for developers navigating its strict rules. By embracing thread confinement, leveraging workers, managing frozen state, and adopting multi-threaded coroutines, developers can build robust multiplatform applications. This talk is a must for Kotlin/Native enthusiasts seeking to master concurrency in modern mobile development.
Links
- Watch the full talk on YouTube
- Touchlab
- American Express
- KotlinConf
- JetBrains
- Kotlin Website
- Kotlin/Native Repository
Hashtags: #KevinGalligan #KotlinNative #Concurrency #Touchlab #JetBrains #Multiplatform
[DevoxxPL2019] Micro Frontends: Extending Service-Oriented Architecture to Frontend Development
Lecturer
Jakub Sowiński is a software architect at StepStone Services, specializing in frontend web development. He joined the company four years prior to his 2019 presentation as a software engineer, focusing on the maintenance and development of their core online job board platform. His work emphasizes architectural transformations from monolithic systems to service-oriented designs, particularly in frontend contexts.
Abstract
This article explores the adoption of micro frontends as an extension of service-oriented architecture to frontend development, drawing from practical experiences at StepStone Services. It examines the rationale, implementation challenges, and benefits of decomposing frontend applications into independent, deployable units. Key concepts such as independence in deployment, team ownership, and progressive refactoring are analyzed, alongside technical strategies for composition, communication, and standardization. The implications for organizational structure, development agility, and system resilience are discussed, highlighting how this approach addresses complexities in large-scale, distributed systems.
Context and Rationale for Micro Frontends
In the evolving landscape of software architecture, the shift from monolithic applications to service-oriented designs has become a cornerstone for managing complexity in backend systems. Jakub extends this paradigm to the frontend, introducing micro frontends as a means to handle the user interface in distributed environments. At StepStone Services, the core application—an online job board—initially presented as a sprawling monolith with millions of lines of code, lacking modularity and separation. This led to challenges in adding features without introducing bugs, slowed release cycles (once weekly), and difficulties in maintaining code quality.
The motivation stems from organizational and technical imperatives. Micro frontends allow for vertically decomposed applications, where each segment encapsulates a specific business logic subdomain, owned by autonomous teams. This fosters expertise within teams, enhances developer satisfaction, and aligns with agile principles by enabling rapid iterations and experiments. Jakub references industry adoption by companies like Facebook and Microsoft, underscoring the traction gained by this approach in recent years, particularly since 2015 when the term gained prominence.
Critically, this method addresses Dan Abramov’s critique, where he questioned the necessity of micro frontends, suggesting component models suffice. Jakub counters that while component models handle modularity, micro frontends tackle broader organizational structures, promoting small, focused teams that deliver end-to-end value. The architecture facilitates progressive refactoring, minimizing risks by isolating dependencies, a vital aspect for legacy systems like StepStone’s.
Implementation Strategies and Technical Solutions
Implementing micro frontends requires a composition layer, often termed a container application or templating engine, to assemble independent micro applications into a cohesive user experience. At StepStone, a modified version of Zalando’s Taylor library handles this, using configuration files to map routes to templates and fragments. Templates are collections of fragments, each with a unique ID linking to specific micro frontends. This server-side composition ensures the end-user perceives a unified website, while under the hood, each micro frontend maintains its own repository, pipeline, and version.
Inter-micro frontend communication poses another challenge. Jakub describes using PubSubJS for publisher-subscriber patterns, where components subscribe to messages (e.g., triggering a login modal). Alternatives like custom events via browser APIs or shared global states (e.g., Redux) are viable, though StepStone favors PubSubJS for simplicity. For development processes, standardization mitigates fragmentation risks. A project creation tool generates skeletons with standardized tech stacks (React, TypeScript, Webpack), build plans (Babel compilation, Jest testing), and deployment options (Node.js for server-side rendering or static assets).
Styling consistency is achieved via CSS-in-JS with styled-components, allowing theme variants for different websites, managed by UX teams. A component library in a monorepo, using tools like Storybook and Lerna, ensures reusability. Testing standardization includes unit tests in build plans and an automated test framework with Selenium, split by subdomains for efficient releases.
Code sample illustrating fragment composition in the templating engine:
// Example route-to-template mapping
const routes = {
'/home': 'homeTemplate',
'/search': 'searchTemplate'
};
// Exemplary template with fragments
const homeTemplate = `
<header id="headerFragment"></header>
<main id="contentFragment"></main>
<footer id="footerFragment"></footer>
`;
// Fragment mapping
const fragments = {
'headerFragment': { url: '/microfrontend/header', config: { /* options */ } },
// Additional fragments...
};
Challenges and Mitigation Approaches
Adopting micro frontends introduces complexities, such as potential fragmentation in processes and technologies. Jakub acknowledges risks like decreased consistency but argues autonomy boosts responsibility and effectiveness. Balancing this requires automation and standards, as seen in StepStone’s tools for project setup and shared libraries (e.g., frontend vendor package for common dependencies like React, reducing bundle sizes).
Performance benefits arise from externalizing shared libraries, cached early in user sessions. For testing, baseline standards ensure coverage, with teams encouraged to experiment (e.g., Cypress). Ownership models, inspired by open-source practices, appoint custodians for shared tools, preventing neglect. Community meetings facilitate alignment, empowering developers to solve common issues collectively.
A key pain point is out-of-order processing resilience. Unlike CRUD systems, where sequential errors corrupt states, log-based approaches (though not directly used here) inspire fault tolerance. Micro frontends’ independence minimizes cascading failures, enabling quicker recoveries.
Implications and Future Directions
The implications extend beyond technical gains to organizational agility. StepStone reduced release times from a week to 30 minutes, enhancing speed without meetings or extensive testing. This supports continuous delivery in large-scale applications, with benefits like progressive refactoring allowing graceful replacements.
However, Jakub cautions that micro frontends suit specific contexts—large, complex applications—not all projects. Starting with monoliths is advisable for simplicity, as premature decomposition increases overhead. Future enhancements could integrate web components for greater tech autonomy, though StepStone prioritizes standardization for collaboration and performance.
In conclusion, micro frontends represent a strategic extension of service-oriented principles, fostering scalable, resilient frontends. StepStone’s journey illustrates practical viability, balancing autonomy with standards to drive innovation and efficiency.
Links:
[KotlinConf2019] Kotlin Multiplatform Programming: Present and Future with Dmitry Savvinov and Liliia Abdulina
Kotlin Multiplatform Programming (MPP) has been a significant focus for JetBrains, aiming to extend Kotlin’s reach beyond traditional JVM and Android development. At KotlinConf 2019, Dmitry Savvinov and Liliia Abdulina, both from the Kotlin team at JetBrains, delivered an insightful overview of MPP’s capabilities in version 1.3.X and offered a glimpse into its future direction. Dmitry Savvinov, a key contributor to Kotlin Contracts and heavily involved in MPP, brought his compiler expertise to the discussion. The official Kotlin language website, the central hub for MPP information, is kotlinlang.org.
Their talk was structured to cover the fundamentals of multiplatform projects, illustrate these basics with examples, provide guidelines for designing first multiplatform projects, and showcase a production-like application to demonstrate design principles in action. This comprehensive approach aimed to equip developers with the knowledge needed to start leveraging MPP effectively.
Core Concepts of Kotlin Multiplatform
Dmitry Savvinov and Liliia Abdulina began by explaining the core building blocks of Kotlin Multiplatform projects. These foundational concepts are crucial for understanding how code can be shared and specialized across different platforms:
* Source Sets: The fundamental unit of code organization in MPP. A project is typically structured with a commonMain source set containing platform-agnostic Kotlin code. Platform-specific source sets (e.g., jvmMain, jsMain, iosMain) contain code tailored for each target and can depend on commonMain.
* Targets: These define the platforms the project will compile for, such as JVM, Android, JavaScript, iOS (Native), Linux, Windows, and macOS. Each target has its own compilations.
* Compilations: A compilation process for a specific target that produces the appropriate artifacts (e.g., JVM bytecode, JavaScript files, native executables).
* expect and actual Declarations: This powerful mechanism allows common code in commonMain to declare an expect class, function, or property. Platform-specific source sets must then provide an actual implementation for that declaration, bridging the gap between shared logic and platform-specific APIs. For example, a common module might expect a function to generate a UUID, and the JVM and Native modules would provide actual implementations using their respective platform libraries.
These concepts enable developers to write shared business logic, data models, and algorithms once in commonMain and then reuse them across multiple platforms, significantly reducing code duplication and improving consistency.
Designing and Implementing Multiplatform Projects
Beyond the basic syntax, Dmitry and Liliia provided guidance on how to approach the design of a multiplatform project. This involved discussing strategies for identifying what code can and should be shared, how to structure modules for optimal maintainability, and best practices for using expect/actual effectively.
They used toy examples to illustrate these basics in a clear and understandable manner, helping attendees grasp how these pieces fit together in a real project. The presentation then progressed to showcase a “more or less production-like application”. This larger example would have served to demonstrate how the design principles discussed could be applied to build a substantial, real-world multiplatform application, highlighting how to manage dependencies, handle platform-specific interactions, and structure a scalable MPP architecture. The focus was on providing practical insights that developers could apply to their own projects, whether starting from scratch or integrating MPP into existing applications.
The Trajectory of Kotlin Multiplatform: Beyond 1.3.X
While detailing the state of MPP in Kotlin 1.3.X, Dmitry Savvinov and Liliia Abdulina also looked towards its future development. At KotlinConf 2019, MPP was still evolving, with ongoing improvements to tooling, library support, and the overall developer experience. Their talk touched upon the roadmap for MPP, including planned enhancements to areas like Kotlin/Native (for performance and interoperability), library ecosystem growth, and further refinements to the build system and IDE support within IntelliJ IDEA and Android Studio.
The vision presented was one of Kotlin as a truly universal language, enabling developers to target a wide array of platforms with a unified codebase and skillset. The commitment from JetBrains to invest heavily in MPP was clear, with the aim of making it a robust and production-ready solution for cross-platform development. The session would have encouraged developers to explore MPP, provide feedback, and contribute to its growing ecosystem, reinforcing the community-driven aspect of Kotlin’s development.
Links:
[SpringIO2019] Zero Downtime Migrations with Spring Boot by Alex Soto
Deploying software updates without disrupting users is a cornerstone of modern DevOps practices. At Spring I/O 2019 in Barcelona, Alex Soto, a prominent figure at Red Hat, delivered a comprehensive session on achieving zero downtime migrations in Spring Boot applications, particularly within microservices architectures. With a focus on advanced deployment techniques and state management, Alex provided actionable insights for developers navigating the complexities of production environments. This post delves into his strategies, enriched with practical demonstrations and real-world applications.
The Evolution from Monoliths to Microservices
The shift from monolithic to microservices architectures has transformed deployment practices. Alex began by contrasting the simplicity of monolithic deployments—where a single application could be updated during off-hours with minimal disruption—with the complexity of microservices. In a microservices ecosystem, services are interconnected in a graph-like structure, often with independent databases and multiple entry points. This distributed nature amplifies the impact of downtime, as a single service failure can cascade across the system.
To address this, Alex emphasized the distinction between deployment (placing a service in production) and release (routing traffic to it). This separation is critical for zero downtime, allowing teams to test new versions without affecting users. By leveraging service meshes like Istio, developers can manage traffic routing dynamically, ensuring seamless transitions between service versions.
Blue-Green and Canary Deployments
Alex explored two foundational techniques for zero downtime: blue-green and canary deployments. In blue-green deployments, a new version (green) is deployed alongside the existing one (blue), with traffic switched to the green version once validated. This approach minimizes disruption but risks affecting all users if the green version fails. Canary deployments mitigate this by gradually routing a small percentage of traffic to the new version, allowing teams to monitor performance before a full rollout.
Both techniques rely on robust monitoring, such as Prometheus, to detect issues early. Alex demonstrated a blue-green deployment using a movie store application, where a shopping cart’s state was preserved across versions using an in-memory data grid like Redis. This ensured users experienced no loss of data, even during version switches, highlighting the power of stateless and ephemeral state management in microservices.
Managing Persistent State
Persistent state, such as database schemas, poses a significant challenge in zero downtime migrations. Alex illustrated this with a scenario involving renaming a database column from “name” to “full_name.” A naive approach risks breaking compatibility, as some users may access the old schema while others hit the new one. To address this, he proposed a three-step migration process:
- Dual-Write Phase: The application writes to both the old and new columns, ensuring data consistency across versions.
- Data Migration: Historical data is copied from the old column to the new one, often using tools like Spring Batch to avoid locking the database.
- Final Transition: The application reads and writes exclusively to the new column, with the old column retained for rollback compatibility.
This methodical approach, demonstrated with a Kubernetes-based cluster, ensures backward compatibility and uninterrupted service. Alex’s demo showed how Istio’s traffic management capabilities, such as routing rules and mirroring, facilitate these migrations by directing traffic to specific versions without user impact.
Leveraging Istio for Traffic Management
Istio, a service mesh, plays a pivotal role in Alex’s strategy. By abstracting cross-cutting concerns like service discovery, circuit breaking, and security, Istio simplifies zero downtime deployments. Alex showcased how Istio’s sidecar containers handle traffic routing, enabling techniques like traffic mirroring for dark launches. In a dark launch, requests are sent to both old and new service versions, but only the old version’s response is returned to users, allowing teams to test new versions in production without risk.
Istio also supports chaos engineering, simulating delays or timeouts to test resilience. Alex cautioned, however, that such practices require careful communication to avoid unexpected disruptions, as illustrated by anecdotes of misaligned testing efforts. By integrating Istio with Spring Boot, developers can achieve robust, scalable deployments with minimal overhead.
Handling Stateful Services
Stateful services, particularly those with databases, require special attention. Alex addressed the challenge of maintaining ephemeral state, like shopping carts, using in-memory data grids. For persistent state, he recommended strategies like synthetic transactions or throwaway database clusters to handle mirrored traffic during testing. These approaches prevent unintended database writes, ensuring data integrity during migrations.
In his demo, Alex applied these principles to a movie store application, showing how a shopping cart persisted across blue-green deployments. By using Redis to replicate state across a cluster, he ensured users retained their cart contents, even as services switched versions. This practical example underscored the importance of aligning infrastructure with business needs.
Lessons for Modern DevOps
Alex’s presentation offers a roadmap for achieving zero downtime in microservices. By combining advanced deployment techniques, service meshes, and careful state management, developers can deliver reliable, user-focused applications. His emphasis on tools like Istio and Redis, coupled with a disciplined migration process, provides a blueprint for tackling real-world challenges. For teams like those at Red Hat, these strategies enable faster, safer releases, aligning technical excellence with business continuity.
Links:
[DevoxxPL2019] Practical Event Sourcing: Avoiding Common Errors in Implementation
Lecturer
David Schmitz functions as a key architect at Senacor Technologies, with deep roots in rebuilding financial platforms using event-driven designs. His work spans multiple large-scale projects in banking and insurance, emphasizing sustainable architectures.
Abstract
This review critiques simplistic event sourcing adoptions, advocating refined tactics derived from extensive enterprise deployments. It defines events as unalterable facts in streams, integrates CQRS for separated concerns, and tackles hurdles in transactions, compensations, and privacy compliance. Via financial illustrations, it inspects techniques for consistency, deletion via tombstones, and polyglot persistence, reflecting on outcomes for durability and adaptability in regulated domains.
Understanding Event-Driven Foundations: Events as Core Artifacts
Event sourcing posits systems as event appendages, reconstructing states from chronological logs. David analogizes to routine transactions, like cafe orders, where each step—ordering, paying—yields events forming the narrative.
In enterprises, particularly finance, this supplants CRUD with append-only logs, yielding audit trails. Streams, via Kafka, order events, partitions grouping related ones (e.g., per account) for sequential processing.
Context: monolithic databases yield to distributed microservices, but naive per-service databases fragment truth. Event sourcing unifies via shared streams, though over-partitioning bloats management.
Analytically, events as facts enable replays for debugging or migrations, but demand schema versioning. Implications: bolsters traceability, yet escalates storage, mitigated by compaction removing intermediates.
CQRS Integration: Decoupling Commands and Queries
CQRS bifurcates modifications (commands appending events) from retrievals (queries on projections). David exemplifies transfers: commands emit events to sender/receiver streams; processors project balances.
Methodologically, Kafka Streams aggregate, ensuring eventual consistency sans distributed transactions. For guarantees, single-writer patterns serialize per-entity events.
Challenges: optimistic concurrency via versions prevents overwrites; failures invoke compensations—explicit reversals preserving history.
In insurance, initial oversights led to redesigns; polyglot views (e.g., Elasticsearch for searches) enhance flexibility.
Analytically, this scales sides independently, but lags demand monitoring. Consequences: agile evolutions, though compensations complicate logic, necessitating clear business rules.
Compliance Handling: Deletion and Rectification in Immutable Logs
GDPR mandates erasure/rectification, clashing with immutability. David proposes tombstones—events signaling deletions, prompting anonymization downstream.
For users, tombstones propagate, purging identifiers while retaining structures for audits. Rectifications append compensations, adjusting views idempotently.
Methodologically, upcasters during replays adapt old events to new schemas, ensuring compatibility.
Analytically, this reconciles permanence with privacy, but requires cross-domain coordination. Implications: legal alignment, though tombstones inflate streams, offset by compaction.
Maintainability and Tradeoffs: Insights from Deployments
Event sourcing decouples, but excessive streams hinder navigation. David advises aligning with bounded contexts, mixing with relational stores for queries.
From banking, optimistic conflicts resolved via retries; security via encryption guards sensitive events.
Methodologically, tools like KSQL query streams declaratively. Implications: audit-ready systems, but complexity in debugging distributed flows.
In reflection, event sourcing thrives with deliberate design, yielding robust, evolvable platforms in stringent sectors.
Links:
[DevoxxPL2019] Constructing Custom Reactive Publishers: Insights into Project Reactor Internals
Lecturer
Oleh Dokuka contributes to Project Reactor as a committer, authoring books on reactive programming with Spring and serving as a software engineer at Superhuman. Based in Kyiv, he actively participates in conferences and communities focused on asynchronous systems.
Abstract
This inquiry delves into the intricacies of building a reactive publisher compliant with Reactive Streams specifications, drawing from Project Reactor’s design. It covers the rationale behind the spec, naive implementations, concurrency patterns like work-in-progress, and verification via TCK. Through iterative coding, it analyzes challenges in non-blocking data flows, backpressure, and thread safety, pondering effects on debugging, customization, and library extension.
Demystifying Reactive Streams: Specification and Purpose
Reactive Streams standardize asynchronous, non-blocking data processing with backpressure, addressing overflow in producer-consumer scenarios. Oleh commences by recalling the spec’s origins, crafted to unify libraries like RxJava and Akka Streams, ensuring interoperability.
Core interfaces—Publisher, Subscriber, Subscription, Processor—define interactions: publishers emit items, subscribers consume, subscriptions mediate requests and cancellations. The spec mandates rules for thread safety and signal ordering, preventing races.
Contextually, adoption surged with Java 9’s Flow API, embedding reactivity natively. Analytically, backpressure—subscribers requesting items—prevents buffering overloads, crucial in unbounded sources like networks.
Implications: enables composable, resilient pipelines, but demands adherence to 50+ rules, tested via TCK. For developers, understanding facilitates debugging; for extenders, it unlocks optimizations.
Naive Publisher Construction: Initial Steps and Pitfalls
Commencing with a basic array publisher, Oleh demonstrates emitting elements on subscription. Yet, naivety ignores concurrency: parallel subscriptions risk duplicates or misses.
Methodologically, extend TCK’s PublisherVerification for rule checks. Initial failures highlight needs for atomic operations and request tracking.
A subscription class manages emissions:
class ArraySubscription<T> implements Subscription {
private final Subscriber<? super T> subscriber;
private final T[] array;
private int index = 0;
private boolean canceled = false;
public ArraySubscription(Subscriber<? super T> subscriber, T[] array) {
this.subscriber = subscriber;
this.array = array;
}
@Override
public void request(long n) {
if (n <= 0 && !canceled) {
subscriber.onError(new IllegalArgumentException("Non-positive request"));
canceled = true;
return;
}
for (long i = 0; i < n && !canceled; i++) {
if (index < array.length) {
subscriber.onNext(array[index++]);
} else {
subscriber.onComplete();
canceled = true;
break;
}
}
}
@Override
public void cancel() {
canceled = true;
}
}
This handles basics but falters under concurrency, necessitating refinements.
Incorporating Concurrency Safeguards: Work-in-Progress and Atomicity
To thread-safely accumulate requests, introduce work-in-progress (WIP)—an atomic counter tracking processing state. Oleh explains: increment WIP to claim emission exclusivity; if non-zero, another thread processes, so defer.
Requests add to a requested counter atomically. On WIP decrement to zero, check if more requests pend, resuming if so.
This pattern, akin to semaphores, ensures single-threaded emission despite multi-threaded requests, averting races.
Analytically, it balances responsiveness and safety, though overflows (Long.MAX_VALUE) signal unbounded requests, potentially overwhelming subscribers.
Implications: facilitates non-blocking I/O, vital for high-throughput, but debugging requires tracing atomics.
Verification and Iterative Refinement: Ensuring Spec Compliance
Leverage TCK for exhaustive testing: extend PublisherVerification, supplying working and failing publishers. Tests validate signals, backpressure, and edge cases like negative requests.
Oleh iterates: failures prompt guards, like canceling on invalid requests. Post-fixes, all pass, confirming robustness.
Methodologically, TCK simulates parallelism, exposing flaws early. For custom operators, similar suites verify.
Consequences: empowers library creation or tweaks, as in optimizing for known guarantees, enhancing performance in specific flows.
Extending to Operators and Libraries: Building Beyond Basics
With a compliant publisher, assemble operators chaining transformations. Oleh hints at flux wrappers, where sources like arrays feed pipelines.
Analytically, operators preserve backpressure, propagating requests upstream. This composability yields expressive, efficient streams.
Implications: demystifies internals, aiding contributions to Reactor or custom variants for niches like low-latency trading.
In conclusion, mastering publishers unlocks reactive potential, transforming complex async into manageable flows.