Full-stack engineer Madrid, CET Open to work

Backend, frontend, and the pipeline that puts them in production.

Java and Spring Boot behind the API, Vue 3 and TypeScript in front of it, and the CI/CD and infrastructure to deliver a feature without a hand-off. Two years in, with a master's in distributed systems behind the reasoning.

Role
Full-stack engineer — end-to-end delivery
Experience
Early career · first professional role 2024
Core stack
Java 21 · Spring Boot · Vue 3 · TypeScript · Docker · Kubernetes
Location
Madrid, Spain · CET/CEST · remote or on-site
Availability
Open to work
permanent, employed
Languages
Spanish, native · English, working language

Whole systems, not slices.

Three pieces of work, each stated the way an engineer would want to read it: the problem, the constraint that made it hard, the decisions worth defending, and the targeted architectural evolution.

meridian · distributed command-and-control platform · 11 spring boot services + vue 3 spa

meridian is a working name. Client programs are under NDA and appear here under aliases.

Eleven services, a decoupled event bus, and strict Proof-of-Concept constraints.

A distributed command-and-control estate publishing domain events across Redis channels, fanned out to a Vue 3 single-page client through a centralised STOMP gateway.

Role

Lead developer for the real-time event backbone and spatial evaluation engine, navigating the transition from early-stage architecture to distributed infrastructure.

Two developers for most of it, and for more than a year the only maintainer of the services. The work started as an inherited system: two front-end applications on one back end, littered with anti-patterns, much of it not working. Alongside feature delivery I ran a migration to raise the coding standards across the estate, and contributed to the front-end architecture and several of its features.

Constraint

Built under strict Proof-of-Concept mandates. The requirements demanded a pseudo real-time, multi-user collaborative platform, while explicit limitations deferred the provisioning of production infrastructure. No Kafka cluster, no dedicated API gateway. The problem was to achieve reliable event distribution on lightweight infrastructure and optimistic network assumptions, without backing the future architecture into a corner.

Architecture worth defending

  • A decoupled producer–consumer transport, isolated from the business logic.

    Services publish domain events without knowing who consumes them. The entity and area services have no knowledge that geofencing exists, so a deployment that does not need geofencing simply does not run it, and one that does subscribes to the channels it cares about. The transport is explicitly a stopgap: because the business logic never touches it, migrating to an enterprise broker changes the transport and not the services.

  • Delivery guarantees added at the application layer, not assumed from the transport.

    Redis pub/sub is fire-and-forget, so a client that is offline when an event is published never receives it. At-least-once redelivery was implemented over the server-to-client hop: pending events queued, explicitly acknowledged by the client, retried on a schedule, with conflicts and failures routed to a dead-letter queue for analysis. Delivery is session-based and channel-separated over STOMP, so an event reaches exactly its intended audience and only while that audience is connected.

  • Command–query separation by structure, deliberately not CQRS.

    Separate command and query controllers and service interfaces over one shared JPA entity model and one repository set. That is command–query separation, not CQRS, which requires separate read and write models — a distinction worth stating rather than blurring. Full CQRS multiplies the classes and tests to maintain, and with one back-end developer on a Proof of Concept, delivery speed was the right trade against textbook purity.

Where it falls short, stated plainly

Two Proof-of-Concept debts, both real. Authentication is centralised at the gateway with no service-layer enforcement behind it, so any caller reaching a service port is trusted. And the application-layer queues simulate, in our own code, what a real broker provides as a primitive.

Both have a mapped answer. Replacing the Redis bus with Kafka is close to a drop-in precisely because event generation is decoupled from the transport: the persistent consumer groups the custom queues currently imitate arrive for free. The centralised authentication moves into a dedicated API gateway, enforcing defence in depth at every service boundary rather than only at the edge.

Stack

JavaSpring BootRedis STOMP / WebSocketMySQLLiquibase Vue 3TypeScript

meridian · front end · vue 3 + typescript single-page client

A live map, and Vue's reactivity kept well away from it.

The command-and-control client: continuously moving entities, geofences and mission state rendered onto coordinated Leaflet maps, fed by a WebSocket stream, with state held in per-domain Pinia stores.

Role

Shared architecture, and worth stating precisely. For most of this work the front end had two developers on it, and my colleague was the larger contributor to the code. The architecture was defined jointly, we reviewed each other's work, and I was the reviewer on every pull request and line that went in. What follows are decisions we made together and both defend, not decisions I made alone.

Constraint

An operational picture cannot stutter. The client renders continuously moving entities onto interactive maps while the same data stays consistent with REST responses arriving in whatever order the network chooses. The framework that makes the rest of the application pleasant to write is actively hostile to the library doing the rendering.

Architecture worth defending

  • Leaflet objects are held outside Vue's reactivity, deliberately.

    Vue creates reactive proxies for all standard object properties. This default behavior conflicts with Leaflet maps. The deep proxies reduce frame rates and break internal Leaflet logic. To prevent this, we explicitly put map objects and feature groups inside markRaw and shallowRef. Our design rule is absolute. The Vue framework holds a pointer to the map for rendering purposes only. The framework does not manage the map as state.

  • Several map instances, one layer manager.

    Main, secondary and split views are separate Leaflet instances sharing a single layer manager supplied by injection, rather than each view owning its own layers. One place decides what is on a map, so two views cannot disagree about what exists.

  • Every inbound event is validated at the boundary.

    Socket payloads pass through explicit type guards, checked against an event-contract package shared with the back end, before they reach a store. A real-time client is fed by a producer it does not control; validating at the edge makes a malformed event a logged rejection instead of a corrupted map.

Where it falls short, stated plainly

Thirty-odd domain stores is a great deal of surface for two people to keep coherent, and the boundaries between some of them are historical rather than designed. The type guards are hand-written, so they can drift from the contract they validate against. Generating them from the shared event types would remove that class of bug entirely, and it is the first thing I would change.

Stack

Vue 3TypeScriptPinia LeafletSTOMP / WebSocketVite

master's thesis · in final phase, ahead of defence · solo

A delivery platform on one machine, where every decision has to be defensible.

A self-hosted continuous integration and delivery platform, designed and built end to end on owned hardware: single-node Kubernetes, a full source-to-deploy toolchain above it, and an architecture that has to survive an examiner as well as a build.

Constraint

One machine, an address that changes on migration, and no budget. A cloud provider usually automates storage, ingress, certificates, and network segmentation. For this project, I built and documented each component by hand. The architecture must also remain hardware-independent. The storage layer must survive a full migration via a simple directory copy.

The harder constraint was the second audience. Every decision needed a defence: not "this works", but "this is correct under these conditions, here is the official source that says so, and here is the alternative I rejected and why".

Architecture worth defending

  • Storage chosen against the default, for a stated reason.

    Statically provisioned volumes with a Retain reclaim policy, rather than the dynamic provisioner. This design deliberately rejects the default dynamic storage provisioner. It implements statically provisioned volumes with a Retain reclaim policy. Node-local storage anchors a workload to one node. This restriction does not affect a single-node system. This setup isolates the complete cluster state under one predictable path. Therefore, hardware migration only requires copying the directory tree. Finally, the data subvolumes operate without copy-on-write. CI servers and image registries execute high volumes of write operations. Disabling copy-on-write prevents severe filesystem fragmentation.

  • Builds that cannot escalate.

    The CI server is configured declaratively as code rather than through its web interface. The system creates a temporary pod for every build and destroys it immediately upon completion. The pipeline also builds images without a container daemon. Mounting the Docker socket into a build agent hands root access to anyone editing a pipeline file. Operating without a daemon completely removes this attack surface.

  • Promotion by digest, rollback by probe.

    Images are tagged by commit SHA rather than a moving tag — for traceability, and because Kubernetes only triggers a rollout when the pod spec actually changes. Artifacts are promoted by digest, so what deploys is bit-for-bit what passed. Per-component progress deadlines turn a stalled rollout into a detected failure instead of an indefinite hang.

The finding worth more than the build

Helm silently ignores value keys a chart does not recognise: no error, no warning, no effect. Three separate incidents traced back to that behaviour, and the rule that came out of it is now standing — render the manifest and check for the expected resource before every install, rather than trusting that a value was read.

Where it falls short, stated plainly

The delivery path is push-based from the CI server. That was a deliberate scope decision rather than a preference. A reconciliation loop is another component to govern and document but it means live state and declared state can drift, and drift has already bitten once. GitOps is what I would build instead.

I would also have front-loaded the network layer rather than retrofitting it. Several days went into debugging an access rule that could not have worked, because a load balancer in the path was masking source addresses. Verifying how the stack actually behaves before writing the specification, rather than after, is the habit this project taught me most expensively.

Infrastructure specifics — network topology, ingress paths and remote-access design — are deliberately not published here. The system is live on hardware I own, and a detailed public description of it is a reconnaissance document. Happy to walk through the whole design in an interview.

Stack

Kubernetes (K3s)HelmJenkins KanikoGiteaNGINX IngressLinux

Reads a system before changing it.

This portfolio includes a separate architectural audit. The audit extracts ten concepts from canonical authors like Meyer, Fowler, and the Gang of Four. I checked these theoretical rules against a real eleven-service codebase. The resulting documentation serves two purposes. First, it verifies the patterns actively functioning in production. Second, it catalogs the missing concepts as targeted areas for future improvement. This analysis proves that recognizing structural limitations is just as important as writing new code.

Depth an interview can test.

languages
Java 21 · TypeScript · SQL · Bash
frameworks
Spring Boot · Spring Security · Vue 3 · Pinia · Leaflet · Vite
data
PostgreSQL · MySQL · Redis · JPA and Hibernate · Liquibase
infrastructure
Docker · Kubernetes (K3s) · Helm · Jenkins · NGINX · Linux
practice
Certified Scrum Master · code review · architecture decision records

listed only where a deep technical interview, edge cases included, would be comfortable. no percentages, no stars.

Two years, stated exactly.

Client programs are under NDA and appear under working names. References on request.
PeriodRoleFocus
jun 2025 — present Software engineer ROSOL Technology, Madrid. Lead developer on the real-time event backbone and spatial evaluation engine; contributor to the front-end architecture.
may 2024 — jun 2025 Intern ROSOL Technology. The same system and the same work from the start: fixing and improving the platform from the inside.
msc
Master's Degree in Distributed and Embedded Systems Software — Polytechnic University of Madrid. In its final phase, ahead of defence.
bsc
Bachelor's degree in Computer Engineering — Universidad Politécnica de Madrid.
certification
Certified Scrum Master.

Hireable from anywhere.

Based in Madrid, on Central European Time. Open to roles in Spain and anywhere else the work is interesting — remote, hybrid or on-site, with relocation open to discussion.

cet/cest · full working-day overlap with europe · partial overlap with the americas

Open to work, and quick to answer.

If the role involves carrying something from an API to a running system, that is the work I want. A straight answer about fit either way, and no long silence.

m.carleropro@gmail.com · linkedin.com/in/m-carlero

Set in Bricolage Grotesque, Source Serif 4 and IBM Plex Mono.

Open to work Email me