Qollabi · Senior Software Developer & DevOps Engineer · Jan 2024 – Apr 2026
Strangler-fig migrationof a 268k-line platform
Qollabi is a multi-tenant B2B SaaS platform for partner and channel sales teams — account planning, objectives and key results (OKRs), joint business plans shared with partners, activities and campaigns, reporting periods, benchmarks, smart updates, bulk import and export, and CRM integrations.
Qollabi — platform scale3 repositoriesSheet 0147
Event-sourced aggregates
~268k lines of PHP across 3,700+ files, ~49k lines of TypeScript in the federated frontend, ~142k lines of JavaScript in the legacy frontend. This is the scale of the platform as I inherited it; my own work is in section 01.
01 · My role
Frontend · backend · platform- Introduced the micro-frontend architecture — a React 18 Module Federation remote consumed at runtime by the React 16 host, so new work could ship without waiting on the legacy release train.
- Maintained and extended the legacy platform — feature work and defect fixing across the Symfony GraphQL backend and the React 16 SPA that sits on it.
- Delivery and infrastructure — the Docker-based development environment, the UAT and production pipelines, the Terraform codebase and the Docker Swarm cluster, plus the VPN and reverse tunnel the team develops through.
- The AWS footprint — Route 53, RDS, Amazon MQ, S3 and IAM.
- Salesforce data migration — the scripts that moved client account, plan and objective data across during onboarding.
02 · Architecture
Strangler fig, enforced in CI- Micro-frontend via Webpack 5 Module Federation — a new React 18 + TypeScript remote exposes routes and components consumed at runtime by the React 16 legacy host, letting both codebases ship independently.
- Strangler Fig migration, enforced in CI —
phpat architecture tests (StranglerFigTest, LayeredArchitectureTest, LegacyArchitectureTest) fail the build when new code depends on legacy layers. - Shadow DOM isolation — federated modules mount inside a shadow root with their own style tree, so Mantine and Tailwind never collide with the legacy Ant Design/Less stylesheet.
- CQRS + Event Sourcing — EventSauce aggregates behind Tactician command/query buses, with dedicated projectors rebuilding MongoDB read models from the event stream.
- Database-per-tenant multi-tenancy — each customer gets its own MongoDB database, resolved per request from an
X-Tenant-Id header via a resettable Doctrine document manager. - GraphQL-first API — a custom Symfony GraphQL bundle serves the schema; the federated frontend consumes it through a GraphQL Mesh gateway with fully typed generated documents.
03 · Repositories
Backend · remote · hostqollabi-sfa-core
Symfony/GraphQL backend, event store, projections, workers, CLI, cron.
qollabi-rja-federation
Module Federation remote: app shell, feature modules (integration-layer, smart-updates, tableau-demo, common), shared packages and Storybook.
qollabi-rja-brm
Mounted as legacy/ — the original React 16 SPA, now the federation host.
04 · Backend
PHP 8.2 · Symfony 5.4Framework
Symfony 5.4 (Flex, Framework/Security/Twig bundles)
Database
MongoDB 7 (replica set), Doctrine MongoDB ODM 2, devture/mongodb-migrations
API
GraphQL — custom qollabi/sfx-graphql-bundle, field-level authorization voters
CQRS
League Tactician (command/query/event buses) + Symfony Messenger with per-bus middleware
Event sourcing
EventSauce 0.8 + Doctrine message repository, projector layer for read models
Queues
RabbitMQ 3 (ext-amqp, kcs/messenger-extra) — quorum queues, dead-letter exchanges, delivery limits, dedicated integration-layer transport
Cache / locks
Redis 5 (predis, snc/redis-bundle), Symfony Lock & Cache
Auth
Auth0 (auth0-php, Management API), firebase/php-jwt, S3-distributed JWT certs
Storage
AWS SDK for PHP + Flysystem (S3 adapter, oneup/flysystem-bundle)
Email
Symfony Mailer + SendGrid, Twig with Inky, CSS inliner and Markdown extras
Spreadsheets
PhpSpreadsheet — bulk account/objective import & export pipelines
i18n
php-translation/symfony-bundle + willdurand/js-translation-bundle (catalogs shared with the frontend)
Feature flags
Opensoft Rollout (qollabi/phx-rollout-bundle)
Observability
Rollbar, Monolog, Blackfire SDK, Symfony Stopwatch/Profiler
Quality
PHPSpec 7, PHPUnit, Symfony Panther, PHPStan level 6, phpat architecture rules, PHP-CS-Fixer, Rector
05 · Federated app
React 18 · TypeScript 5.3Core
React 18, TypeScript 5.3, Webpack 5
Federation
@module-federation/enhanced — remote exposing modules & components to the legacy host
Monorepo
Yarn workspaces — 4 feature modules, 11 shared packages, 6 shared build configs, 1 Storybook workspace
Components
Mantine 7 (core, dates, hooks, notifications), mantine-datatable, PrimeReact 10 — wrapped in a shared @qollabi/ui design system
Styling
Tailwind CSS 3, PostCSS (postcss-preset-mantine), Sass, class-variance-authority, clsx, tailwind-merge
Server state
TanStack Query 5 (suspense queries) over graphql-request + TypedDocumentNode generated documents
Forms
React Hook Form 7 + Yup resolvers
i18n
i18next + react-i18next (shared @qollabi/i18n)
Icons
FontAwesome Pro (light/regular/solid), Tabler Icons
Misc
react-error-boundary, notistack, SortableJS, Zod (build-time env-var validation)
Tooling
Storybook 7, Jest + ts-jest, ESLint + Prettier shared configs, Husky
06 · Legacy app
React 16 · the federation hostCore
React 16, Webpack 5 (migrated off CRA), Babel, partial Flow typing
Data
Apollo Client 2 / react-apollo 3, GraphQL, 70 .graphql documents, batched HTTP link
Components
Ant Design 3 (Less theming), PrimeReact 5
Forms
Final Form + react-final-form (arrays, focus, field-data), React Hook Form 5, Yup, validator
Rich text
TipTap 2 (mentions, links, lists) and Slate 0.82
Interaction
react-beautiful-dnd, react-dnd, react-sortable-hoc, react-virtualized, react-dates
Dates
Moment + moment-timezone + moment-range, @vvo/tzdb
Auth
Auth0 React SDK, jsonwebtoken
Analytics
Rollbar, FullStory, Smartlook, Intercom
Testing
Cypress 4 + Mocha JUnit reporters, Storybook 7
07 · Infrastructure & DevOps
Swarm · GitLab CI · Ansible- Docker Compose local stacks driven by Makefile targets — PHP-FPM, Nginx, MongoDB replica set, RabbitMQ, Redis and mongo-express on the backend; remote + legacy dev servers, Storybook and a tunnel container for Auth0 callbacks on the frontend.
- GitLab CI/CD — install → lint → PHPStan → PHPSpec/PHPUnit with coverage → tagged multi-image build (php, nginx, cron) → deploy → post-deploy validation, with UAT/PRD tag-format gating.
- Docker Swarm deployment —
prd.yaml / uat.yaml stacks with separate images per role and dedicated start scripts for FPM, async workers, import workers, queue setup and migrations. - Ansible + AWS CloudFormation — provisioning playbooks across nine production instances plus preprod, review and demo environments, with Supervisor-managed worker processes.
- Cron image running per-customer scheduled jobs (due-date emails, responsible-assignment notifications, hourly smart-update processing) with Slack log channels.
- Quality gates — PHPStan baseline, architecture tests, CodeClimate, Husky + lint-staged and Prettier across both repos.
08 · Engineering highlights
Six decisionsRuntime micro-frontend bridge
A remoteLazyComponent / remoteLazyModule HOC pair on the legacy side lazily pulls federated exports and injects host context (language, router history, tenant, translation mode), so new React 18 modules render as first-class citizens inside a React 16 app.
Architecture tests as migration policy
The strangler-fig boundary is a build-breaking rule, not a convention, which keeps a 268k-line legacy codebase from re-entangling itself.
Outbound integration layer
18 typed routing keys over a direct RabbitMQ exchange, each with quorum queues, dead-letter routing and a custom message serializer, pushing plans, objectives, activities and access control to external CRMs.
Salesforce OAuth in the browser
The integration is configured per tenant with no server-side credential store, so the flow runs client-side — credentials are encrypted before the token ever reaches browser storage.
Projection replay tooling
CLI commands to rebuild read models, replay benchmark history and repair event versions against a live event store.
Per-customer batch orchestration
A single cron entry fans a console command across every tenant database, so scheduled work scales with customer count without extra infrastructure.