How the services fit together
We run two generations of platform at once. Almost every confusing thing about our architecture follows from that, so it is the first thing worth understanding.
The legacy NettiX estate serves the marketplaces today: PHP and Symfony, Aurora MySQL, Elasticsearch. Mobility One is what replaces it: Next.js, PostgreSQL, OpenSearch. Neither is a rewrite of the other in place — they run side by side, and a set of shared services sits under both.
The two generations
Section titled “The two generations” LEGACY NETTIX MOBILITY ONE ADJACENT: PRO / DEALER ───────────── ──────────── ────────────────────── who consumers consumers dealers │ │ │ ▼ ▼ ▼ web nettix-web, nettix-www m1-web apps aso-* (NettiXPro) Symfony / PHP Next.js on ECS Fargate nettix-webstore (TEHO) │ │ via m1-api-client ▼ ▼ API nettix-api ◀── apps-api ──▶ APIv2 │ ▲ │ ▼ │ ▼ data Aurora MySQL │ PostgreSQL ──sync──▶ OpenSearch Elasticsearch │ (m1-atlas) │ mobile apps (iOS, Android)
shared by both generations: image service, ajomark-header, counter-service, event bus, SalesforceRead it as a grid. Each column is one generation, and each row is one layer: who uses
it, the web tier, the API tier, the data tier. The web, API and data rows summarise
the tables below, which add the layers the picture leaves out. The
right-hand column is the professional and dealer side, which belongs to neither
generation. apps-api sits between the two API tiers because it is the backend for the
mobile apps and reads from both.
Legacy NettiX
Section titled “Legacy NettiX”| Layer | Repositories |
|---|---|
| Web | nettix-web (responsive), nettix-www (classic monolith) |
| API | nettix-api (api.nettix.fi), apps-api (mobile), admin-api |
| Auth | auth-api |
| Data | Aurora MySQL (via RDS); Elasticsearch for search on Nettivene, Nettikone and Nettivaraosa only; caching in Redis, APCu and Memcache |
The verticals are split across the two web codebases rather than migrated cleanly, and the split moves as verticals are ported. Check which codebase a vertical is on before assuming: the glossary has the current mapping.
Mobility One
Section titled “Mobility One”| Layer | Repositories |
|---|---|
| Web | m1-web — one Turborepo monorepo holding per-vertical and multi-tenant Next.js apps on shared UI, deployed to ECS Fargate |
| API client | m1-api-client — the typed npm client for APIv2 |
| Data | m1-atlas — PostgreSQL as the system of record, schema and migrations as code |
| Search | OpenSearch as the read side, synced from Postgres |
Two decisions explain most of its shape, and both are recorded rather than folklore: ECS Fargate over Lambda (m1-web ADR-0001), and Postgres as the system of record with OpenSearch as a read side (m1-atlas ADR-0013). The decision index lists the rest.
One data-model consequence is worth knowing before you read any M1 code: a vehicle and a listing are separate, the vertical lives on the listing, and one vehicle can carry several listings (m1-atlas ADR-0011). Multi-tenancy is a property of the data, not of a per-vertical deployment.
What is shared across both
Section titled “What is shared across both”These are the services that make “which generation is this” the wrong question.
| Service | Does |
|---|---|
alma-mobility-image-service |
Serves and transforms every listing image, routed per vertical by an x-vertical-id origin header behind CloudFront |
ajomark-header |
The global site header, as a library both generations consume — so navigation stays identical while the pages underneath differ |
counter-service |
View and event counting — see Analytics and experimentation |
ajo-event-relay + ajo-event-rules |
Get events out of the legacy AWS estate and onto the central Alma event bus, and route them there |
alma-ajo-salesforce |
Salesforce as the customer-relationship (CRM) and dealer system of record |
The header is the clearest example of the pattern we reach for: rather than duplicating a component per vertical per generation, one library is published and both consume it (m1-web ADR-0012).
Platform services underneath everything
Section titled “Platform services underneath everything”Neither generation owns these; both sit on them.
| Concern | What we use |
|---|---|
| Compute | Elastic Container Service (ECS) with Docker, plus EC2 for the older estate |
| Messaging | The AWS messaging services — SNS, SQS and EventBridge, which carries the central event bus |
| Delivery | CloudFront and Cloudflare in front of the sites |
| Search | Elasticsearch on the legacy side, OpenSearch on the Mobility One side |
| Infrastructure as code | Terraform — infra for the shared estate, infra-v2 for the split-account estate, infra-cloudflare for Cloudflare, and embedded Terraform in individual service repositories |
Analytics and experimentation
Section titled “Analytics and experimentation”Measurement is its own small estate, and it is the part people most often do not know exists until a number looks wrong.
| Piece | What it does |
|---|---|
counter-service |
View and event counting. The Frontend Counter Service (FECS) runs at the edge on Cloudflare Zaraz, through API Gateway into DynamoDB; the Backend Counter Service (BECS) is DynamoDB behind Lambda |
| Google Tag Manager | Analytics event tracking on the sites |
| AB Tasty | A/B testing and experimentation |
Adjacent products
Section titled “Adjacent products”Products with their own stacks, which the two-generation split does not describe well. Some carry their own Jira project and some do not — the Jira projects table is the list that owns that.
- Autotalli — a Java stack, its own Terraform, its own project (AT).
- Baana — the consumer-to-business auction site, TypeScript front and back (AJOC2B).
- OmaAjo (
my-vehicles) — consumer vehicle management, React and PHP. - NettiXPro and TEHO — the professional and dealer side, at
app.nettixpro.fi(theaso-*repositories) andteho.nettix.fi(nettix-webstore).
Machine learning
Section titled “Machine learning”Each of these is a service the marketplaces call, not a platform layer:
ajo-kisu-api
(recommendations from the KISU model),
spede (user trust scoring),
neural-engine-for-registration-obfuscation
(plate blurring), and Hakuapuri (semantic search, in the
almamedia organisation).
Where the boundaries are
Section titled “Where the boundaries are”Six external systems own data we do not:
- Alma Tunnus — end-user SSO, provided centrally by Alma. Unrelated to the AWS SSO engineers use.
- Trafi — the Finnish vehicle-data API. Vehicle facts originate there.
- MAJO — Alma Media’s internal vehicle-data service. A second origin for vehicle facts alongside Trafi, so “where did this car’s data come from” has two answers.
- Salesforce — the customer-relationship (CRM) records.
- WebSales Cloud — the dealer management system.
- Sourcepoint — the consent management platform (CMP) behind the cookie and tracking consent the sites collect.
- Configure AWS SSO for the CLI — to see any of it running.
- Environments — the environment names, and how a resource name tells you which one you are looking at.
- Repository map — every repository named above, with its default branch.