Advanced System Design Tutorial 0/70 lessons ~6 min read Lesson 34

    API Gateway and Backend for Frontend

    TechLearningPro’s course home screen is three different products that happen to share a catalog. The web app shows a syllabus, an instructor bio, and a progress bar. The phone app

    Course progress0%
    Focus
    15 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    API Gateway and Backend for Frontend

    Advanced System Design & Software Architecture · Lesson 34 · High-Level Design and Distributed Systems

    Lesson Overview

    TechLearningPro’s course home screen is three different products that happen to share a catalog. The web app shows a syllabus, an instructor bio, and a progress bar. The phone app shows a resume card, a download flag, and a short title. A university partner wants a stable HTTPS API for enrollments, with client credentials, and must never see the mobile card shape. Learners hit one hostname. Internal services — catalog, progress, enrollment — must not be reachable from the public internet, and they must not each grow a special case for every screen.

    Two roles get confused because both sit “in front.” An API gateway is the edge. It terminates TLS, authenticates the caller, rejects anonymous traffic, routes by path or host, enforces coarse limits, and forwards to a small set of front doors. A backend for frontend (BFF) is one of those front doors, owned by a client team, shaped for one experience. The web BFF may call catalog and progress and assemble WebCourseHome. The mobile BFF assembles MobileCourseHome. The partner API is a third front door with a different credential and no UI aggregation at all.

    The failure mode this lesson is written to prevent is the god gateway: one platform team, one deployable, and a route table that contains screen-specific JSON reshaping, per-client if-statements, and a ticket queue of UI changes. Every mobile release waits on the gateway. A defect in a partner transformation takes down learner login. The edge stays thin. Client-specific composition lives in the BFF that ships with that client. Shared concerns — TLS, JWT validation, routing, request-size caps, and the coarse rate limit from Rate Limiting, Throttling and Load Shedding — stay at the gateway.

    The Java edge in this lesson is Spring Cloud Gateway, which is a Spring WebFlux application. Routes are RouteLocator definitions or spring.cloud.gateway.routes entries with predicates such as Path and filters such as StripPrefix. Authentication uses Spring Security’s resource-server JWT support (oauth2ResourceServer) against a real issuer and JWKS. BFFs are ordinary Spring MVC services using RestClient. Nothing here is a fictional gateway SDK. When these front doors later sit on independently deployed services, they are the HTTP half of Microservices Architecture Principles. Contract evolution is API Design, Versioning and Contracts. Identity beyond the edge is Microservices Security and Zero Trust.

    Learning Objectives

    By the end of this lesson, you should be able to:

    1. Separate edge concerns (TLS, authentication, routing, coarse limits) from experience concerns (aggregation, client-specific payloads).
    2. Place one BFF per client experience, with a named owning team, and keep the partner API off the mobile BFF.
    3. Validate a JWT at the gateway with issuer, signature via JWKS, expiry, and audience, using Spring Security’s resource server.
    4. Route with Spring Cloud Gateway predicates and filters without embedding screen logic in the route table.
    5. Fan out from a BFF with timeouts, and return a partial page when a secondary dependency fails and an error when the primary one fails.
    6. Explain why a god gateway becomes a release bottleneck and an availability coupling.
    7. State what the gateway must not do: own business data, hide a failing primary resource behind an empty 200, or retry unsafe HTTP methods.
    8. Connect the edge to the microservices module: independent BFF deploys, versioned contracts, and downstream services that still authenticate.

    Prerequisites

    • Lesson 26, Load Balancing and Traffic Management: the gateway usually sits behind a load balancer. The balancer spreads connections. The gateway authenticates and routes.
    • Lesson 6, Hexagonal Architecture and Ports & Adapters: catalog and progress remain the systems of record. A BFF is an adapter for a client, not a new source of truth.
    • Lesson 9’s rule that synchronous calls are for answers the user is waiting on. The course home is one of those calls. Enrollment events stay on the log from lesson 33.
    • Familiarity with HTTPS, HTTP status codes, and a JWT as a signed set of claims (iss, aud, exp, sub). You do not need to operate an identity provider. You do need to know the gateway fetches keys from the issuer rather than embedding a public key in a source file that nobody rotates.

    Detailed Core Concepts

    The edge and the experience

    ConcernGatewayBFFDownstream service
    TLS termination for browsers and appsYesUsually inside the private networkPrivate
    Authenticate the callerYesReceives an already-authenticated call, and still verifies the token if it is a resource serverYes, in a zero-trust deployment
    Route /web and /mobile and /partnerYesNoNo
    Coarse rate limit and body-size capYesOptional finer limitResource-specific limit (lesson 35)
    Assemble a screenNoYesNo
    System of record for progress or catalogNoNoYes

    Sam Newman’s BFF description, building on Phil Calçado’s SoundCloud write-up, is the definition used here: a BFF is the API that one user experience talks to, owned by the team that ships that experience. It is allowed to be “selfish.” The mobile BFF may drop fields the phone will not render. The web BFF may include an HTML-oriented syllabus the phone does not want. Selfish does not mean duplicated business rules. Whether a learner may enroll is enrollment’s decision. The BFF displays the decision.

    A single shared “public API” that every client calls is reasonable when the clients truly want the same resource model, as the partner API does. It becomes a compromise API when web and mobile pull it in opposite directions: optional fields, flags named includeSyllabus, and version parameters that mean “mobile 4.2.” Those flags are a BFF that has not been allowed to exist, hiding inside a gateway or a general-purpose API.

    What the gateway actually terminates

    TLS. Learners connect to https://api.techlearningpro.example. The certificate lives on the load balancer or on the gateway. Upstream hops to web-bff run on the private network. Whether that hop is plaintext on a locked-down network or mTLS is a platform decision. Lesson 44 treats mTLS and workload identity in depth. This lesson’s requirement is narrower: public clients never speak plaintext, and internal services have no public listener.

    Authentication. TechLearningPro issues JWTs. Spring Security, configured as an OAuth 2.0 resource server with spring.security.oauth2.resourceserver.jwt.issuer-uri, loads the issuer’s OpenID Provider configuration and JWKS, and checks signature, issuer, and expiry. That default does not check audience. Add an OAuth2TokenValidator<Jwt> that requires the aud claim to contain an audience you named, such as techlearningpro-api. A token minted for a different product at the same identity provider must not call this gateway. Partner integrations use a different audience, or a different issuer, and a different route. Do not accept a shared secret in a header as a substitute for this check on the public edge.

    Routing. A predicate matches the request. Path=/web/** selects the web BFF. Path=/mobile/** selects the mobile BFF. Host=partner.techlearningpro.example selects the partner front door. StripPrefix=1 removes /web or /mobile so the BFF does not re-implement the edge prefix. Filters that add a correlation id are appropriate. Filters that reshape a syllabus into a mobile card are not.

    Rejection. Unknown routes return 404 from the gateway, not a default proxy to “whatever is behind.” Unauthenticated calls return 401. Calls with a token for the wrong audience return 401 or 403. Oversized bodies are rejected before they reach a BFF. The gateway is allowed to be boring and strict.

    Aggregation, timeouts, and partial failure

    The web course home needs catalog (primary) and progress (secondary). The BFF calls both. A sequential pair of 300 ms calls costs up to 600 ms before rendering. Parallel calls with CompletableFuture and a per-call timeout keep the page inside a latency budget you can write down: catalog timeout 400 ms, progress timeout 200 ms, page budget 500 ms.

    Failure policy is part of the API, not an afterthought:

    • Catalog timeout or 5xx: the page has no course. Return 502 or 503. Do not invent a course title.
    • Progress timeout or 5xx: return 200 with the course and degraded: ["progress"]. The UI shows the syllabus and a progress placeholder. The learner can still read.
    • Progress 404: the learner has no progress row. That is an empty state, not a degradation. Return progress as “not started.”

    The BFF does not retry POST. A GET to catalog may be retried once, and only if the call failed before a response (connect timeout), because a read is safe to repeat. Spring Cloud Gateway’s Retry filter, if you enable it, must list methods and statuses. Retrying POST at the gateway duplicates enrollments. Enrollment idempotency is lesson 37; the gateway’s job is to not make the duplicate more likely.

    Timeouts need budgets that add up. If the gateway’s response timeout is 1 s and the BFF waits 2 s on catalog, the learner sees a gateway timeout while the BFF is still working. Set the gateway’s upstream response timeout above the BFF’s page budget, and set each downstream timeout below that budget. Cancel or abandon late responses so a slow progress call cannot occupy a BFF thread after the page has already been sent.

    The god gateway

    A god gateway starts innocently. The first mobile requirement is “drop three fields.” Someone adds a response filter. The next requirement is “if the app version is 4.x, rename title to name.” The route table gains a programming language. The platform team becomes the bottleneck for two client teams. Operationally, the process is in the request path of every client, so a bad transformation deploy is a total outage. Cognitively, nobody can tell which team is allowed to change a route.

    The split that stays reviewable:

    • Platform owns the gateway: certificates, issuer and audience, the three route prefixes, body limits, coarse quotas, access logs, and the pager for edge availability.
    • The web team owns web-bff and WebCourseHome.
    • The mobile team owns mobile-bff and MobileCourseHome.
    • The partnerships team owns the partner API process, its client-credentials audience, and its quota.

    A change to the mobile card ships with the mobile BFF. It does not require a gateway release. A new internal service is called by a BFF, not published on the public DNS.

    What crosses the trust boundary

    Forward the caller’s Authorization header to the BFF and, if downstream services are resource servers, onward to catalog and progress. Each hop validates the JWT. That is more work than a gateway that strips the token and injects X-User-Id. The header approach is safe only when clients cannot reach the BFF except through the gateway, and the gateway overwrites any client-supplied X-User-Id. One missed network policy and any caller picks the user id. Prefer token forwarding until lesson 44 gives you a workload identity you can name. Never log the raw token. Log the subject claim and the correlation id.

    The BFF is not a place to assemble a “convenient” object that includes another learner’s progress “for the instructor view” unless an authorization check in the owning service allowed it. Aggregation does not bypass authorization. If the instructor view is a different authorization, it is a different call to progress, not a join the BFF performs in its own database. The BFF should not have a database of progress. A short private cache of catalog GETs is optional and must follow Caching Strategies and Cache Invalidation. Cached authorization decisions are how stale permissions survive a revocation. Do not cache those here.

    Architecture and Design Diagrams

    mermaid
    flowchart LR
    subgraph public [Public internet]
    Web[Web app]
    Phone[Mobile app]
    Partner[Partner system]
    end
    LB[Load balancer TLS]
    GW[Spring Cloud Gateway]
    Web --> LB
    Phone --> LB
    Partner --> LB
    LB --> GW
    GW -->|/web| WB[web-bff]
    GW -->|/mobile| MB[mobile-bff]
    GW -->|partner host| PA[partner-api]
    WB --> CAT[catalog]
    WB --> PR[progress]
    MB --> CAT
    MB --> PR
    PA --> EN[enrollment]
    mermaid
    sequenceDiagram
    participant C as Web app
    participant G as Gateway
    participant B as web-bff
    participant Cat as catalog
    participant P as progress
    C->>G: GET /web/courses/42 Authorization Bearer
    G->>G: JWT issuer, signature, exp, aud
    G->>B: GET /courses/42 forward Authorization
    par Fan-out
    B->>Cat: GET /courses/42
    B->>P: GET /progress?courseId=42
    end
    Cat-->>B: 200 course
    P-->>B: timeout
    B-->>G: 200 WebCourseHome degraded progress
    G-->>C: 200
    mermaid
    flowchart TB
    Q[New client requirement] --> W{Who owns the shape?}
    W -->|Screen fields| BFF[Change that client's BFF]
    W -->|New public prefix or auth rule| EDGE[Change the gateway]
    W -->|Business rule| SVC[Change the owning service]
    W -->|Several clients, same resource| API[Version the shared contract]

    Trust boundary: only the load balancer and gateway accept public traffic. Security groups or network policy block direct access to BFF and service ports. The gateway discards hop-by-hop headers it did not create. Downstream audience checks reject a token that was only meant for the partner host.

    Technical Explanation

    Routes you can deploy

    Spring Cloud Gateway matches the first applicable route. Keep the table short enough to read in review.

    yaml
    spring:
    cloud:
    gateway:
    httpclient:
    response-timeout: 2s
    connect-timeout: 200
    routes:
    - id: web-bff
    uri: http://web-bff:8080
    predicates:
    - Path=/web/**
    filters:
    - StripPrefix=1
    - id: mobile-bff
    uri: http://mobile-bff:8080
    predicates:
    - Path=/mobile/**
    filters:
    - StripPrefix=1
    - id: partner-api
    uri: http://partner-api:8080
    predicates:
    - Host=partner.techlearningpro.example

    StripPrefix=1 removes the first path segment. /web/courses/42 arrives at the web BFF as /courses/42. The BFF does not know it lives behind /web, so the same service is testable without the gateway. response-timeout is the gateway’s wait for the upstream. Two seconds sits above the BFF page budget of about 500 ms and still fails a stuck BFF. Connect timeout is short because a refused connection will not improve if you wait.

    The same routes in Java use RouteLocatorBuilder:

    java
    @Bean
    public RouteLocator learnerRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
    .route("web-bff", r -> r.path("/web/**")
    .filters(f -> f.stripPrefix(1))
    .uri("http://web-bff:8080"))
    .route("mobile-bff", r -> r.path("/mobile/**")
    .filters(f -> f.stripPrefix(1))
    .uri("http://mobile-bff:8080"))
    .build();
    }

    Prefer the YAML when routes are data the platform reviews. Prefer the builder when a route must be computed. Do not generate a route per course id. That is a catalog query, not a route.

    A Retry filter belongs only on idempotent methods. If you add one, restrict it:

    yaml
    filters:
    - name: Retry
    args:
    retries: 1
    methods: GET
    statuses: BAD_GATEWAY,GATEWAY_TIMEOUT

    POST enroll stays without that filter. The BFF and enrollment service apply lesson 37’s idempotency key instead.

    JWT at the edge

    yaml
    spring:
    security:
    oauth2:
    resourceserver:
    jwt:
    issuer-uri: https://auth.techlearningpro.example
    java
    @Bean
    public SecurityWebFilterChain edgeSecurity(ServerHttpSecurity http) {
    http.authorizeExchange(ex -> ex
    .pathMatchers("/actuator/health").permitAll()
    .anyExchange().authenticated())
    .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
    }

    SecurityWebFilterChain and ServerHttpSecurity are the WebFlux types Spring Cloud Gateway requires. A servlet SecurityFilterChain will not secure this process. Health is anonymous so the load balancer can probe it. Everything else requires a valid JWT. Compose the decoder so audience is checked: start from JwtValidators.createDefaultWithIssuer, then add your OAuth2TokenValidator<Jwt> for aud, and set both on the NimbusJwtDecoder built from the issuer location. The validator is a class you write. Spring Security does not ship an AudienceValidator type under that name. Reject missing aud, a string audience that does not match, and a list audience that does not contain techlearningpro-api.

    Partner tokens fail this validator on the learner host on purpose. The partner host configures the partner audience. One gateway process can host both if the security matcher is per path, but two deployments are easier to reason about when the trust policies differ. Start with one gateway and two matchers only if the team can point at the code that selects the audience. If that code is a tangle, split the processes.

    BFF fan-out

    The web BFF is Spring MVC. It is not another gateway. It has no route table for the public internet.

    java
    public CourseHomeView load(String courseId, String authorization) {
    CompletableFuture<CourseSummary> course =
    supply(() -> catalog.get(courseId, authorization)).orTimeout(400, TimeUnit.MILLISECONDS);
    CompletableFuture<ProgressSnapshot> progress =
    supply(() -> progressClient.get(courseId, authorization)).orTimeout(200, TimeUnit.MILLISECONDS);
    CourseSummary summary = course.join();
    ProgressSnapshot snapshot = progress.exceptionally(ex -> null).join();
    List<String> degraded = snapshot == null ? List.of("progress") : List.of();
    return new CourseHomeView(summary, snapshot, degraded);
    }

    catalog.get throwing fails the join, and the controller maps that to 502. Progress failing becomes a null section. Map a progress 404 to a real “not started” snapshot inside progressClient, so a missing row is not reported as degraded. Use a bounded executor you own, not the common ForkJoin pool, so a slow downstream cannot starve the process. Set connect and read timeouts on the RestClient request factory below the orTimeout values, or you will have threads still blocked after the future has already timed out.

    CourseHomeView is the web contract. MobileCourseHome is a different record: courseId, title, resumePositionSeconds, downloadable, degraded. The mobile BFF can call the same catalog and progress URLs. It maps the fields it needs. It does not ask catalog to add ?view=mobile.

    Versioning without a second god object

    Client apps in the store lag the server. The mobile BFF can keep an old response shape until the oldest supported app is gone, because that BFF is the only caller of that shape. The partner API cannot: partners code against a published contract. That contract is versioned in lesson 40, with explicit /v1 or content negotiation, a compatibility policy, and a sunset header when you mean it. Do not version the gateway. Version the front door that partners compile against. Internal calls from BFF to catalog can evolve faster if the BFF and catalog deploy together or if they honor the same compatibility rules. Write the rule down either way.

    Practical Implementation or Design Walkthrough

    Problem

    Design the public HTTP edge for TechLearningPro’s course home on web and mobile, plus a partner enrollment API. Peak is 1,500 course-home reads per second across clients during an evening session, and a few dozen partner calls per second. Learners authenticate with a JWT. Partners authenticate with a client-credentials token. Catalog and progress are separate services. The course home must appear within 500 ms p95 when both dependencies are healthy.

    Requirements

    • Functional: GET /web/courses/{id} returns syllabus-oriented data and progress. GET /mobile/courses/{id} returns the resume card. Partner enrollment is POST on the partner host and is out of the UI BFFs.
    • Security: public TLS, JWT on every learner route except health, audience enforced, no direct public access to catalog or progress.
    • Latency: p95 under 500 ms for the home read when dependencies meet their own budgets. Gateway returns within 2 s even if a BFF hangs.
    • Availability: a progress outage degrades the page. A catalog outage fails the page. A bad mobile BFF deploy does not black-hole the web app.
    • Modifiability: a mobile field change ships without a gateway release.

    Assumptions

    • An identity provider already exposes an issuer and JWKS. This lesson does not invent the provider’s token endpoint.
    • Catalog GET /courses/{id} and progress GET /progress?courseId={id} exist as internal HTTP APIs. The BFF uses RestClient with the base URL from configuration.
    • The private network blocks everyone except the gateway from reaching BFF ports, and blocks everyone except the BFFs from reaching catalog and progress. Partner-api is the only caller of the partner enrollment route on the enrollment service.

    Request path

    1. The client opens TLS to the load balancer and sends GET /web/courses/42 with Authorization: Bearer.
    2. The gateway validates the JWT. A bad token stops here with 401.
    3. The Path=/web/** route and StripPrefix=1 forward GET /courses/42 to web-bff, including Authorization.
    4. The BFF reads sub from the token only after validation (it is also a resource server, or it trusts the gateway’s network path and still validates — pick validation at the BFF too if the network control is new).
    5. Parallel catalog and progress calls. Catalog must succeed. Progress may degrade.
    6. The BFF returns WebCourseHome. The gateway streams it back. Access logs contain subject, route id, status, and latency. They do not contain the bearer token.

    Failure handling

    FailureLearner-visible resultOwner
    Expired or wrong-audience JWT401 from the gatewayIdentity / platform
    No route404 from the gatewayPlatform
    Catalog 5xx or timeout502 from the web BFFCatalog team, page fails
    Progress 5xx or timeout200 with degraded: ["progress"]Progress team, UI placeholder
    Mobile BFF down502 on /mobile onlyMobile team
    Gateway downAll public HTTP failsPlatform, multi-instance gateway behind the load balancer

    Deploy at least two gateway instances. A single gateway process is a total outage waiting for a node loss. BFFs also run at least two instances. The load balancer’s health check uses /actuator/health and removes an instance that fails it. Do not point the health check at catalog, or a catalog incident will drain the gateway and hide the partial-failure behavior you designed.

    Tests

    • A token with the wrong aud receives 401 and produces no BFF access log line.
    • GET /web/courses/42 reaches the BFF as /courses/42.
    • With progress delayed past 200 ms, the body is 200 and degraded contains progress. Catalog was called.
    • With catalog delayed past 400 ms, the status is 502 and the body is not an empty course.
    • POST /web/enrollments is not retried by the gateway. Assert one upstream request in a test double.
    • A direct call to the BFF port from a test that simulates the public network is refused. That test belongs in the environment, not only in a mock.

    Enterprise Real-World Scenarios

    Scenario 1: Web and mobile course home

    Business context. Evening traffic is mostly phones. Web is the rest. Product wants a syllabus module on web next week and a download badge on mobile the week after. Catalog already stores both facts. Progress stores resume position.

    Scale. 1,500 home reads per second. Catalog p95 is 40 ms when healthy. Progress p95 is 30 ms. The BFF’s parallel fan-out fits the 500 ms page budget with room for GC and the gateway hop.

    Choices. Two BFFs, one gateway, StripPrefix, JWT audience techlearningpro-api, progress marked secondary, catalog primary. The mobile team ships the badge in mobile-bff without a platform deploy. The web team’s syllabus change does not touch the phone payload, so the app store is not coupled to the web release.

    What fails review. One “experience API” in the gateway process with if (client == MOBILE). A cache of the entire home response keyed only by course id, shared across learners, leaking one learner’s progress to another. A gateway retry on the enrollment POST that sits next to the home GET.

    Scenario 2: The university partner

    Business context. A university’s student-information system enrolls cohorts via API. Calls are server-to-server, a few dozen per second, with spikes when a registrar uploads a term. The contract is stable for a year. The payload is an enrollment request, not a course home.

    Choices. Hostname partner.techlearningpro.example routes to partner-api, not to mobile-bff. Tokens use client credentials and audience techlearningpro-partner. The partner front door checks that the token’s subject is a registered integration and that the body refers to a course the contract allows. It calls enrollment. It does not call progress and does not import MobileCourseHome. Quota is separate from the learner quota so a registrar upload cannot exhaust the learners’ edge budget; the mechanism is lesson 35. The published contract is versioned under lesson 40. A breaking change is a new version and a sunset, not a quiet field rename inside the gateway.

    The microservices bridge is direct. Partner-api, web-bff, and mobile-bff deploy on their own pipelines. Catalog does not know their names. Service discovery in Service Discovery and Configuration Management can replace the static uri: http://web-bff:8080 when those processes move. The route’s job stays the same. Discovery does not decide the payload.

    Best Practices

    • Keep the gateway’s route table enumerable in one review: prefixes, hosts, timeouts, auth, limits.
    • Give each user experience a BFF with a team that ships it. Let the BFF be selfish about fields and strict about not owning business rules.
    • Validate issuer, signature, expiry, and audience. Forward the bearer token. Do not invent a spoofable user-id header as the only identity.
    • Classify dependencies as primary or secondary. Fail the call when the primary fails. Degrade when the secondary fails. Document the JSON that marks degradation.
    • Bound every hop: connect timeout, read timeout, gateway response timeout, and a page budget that is the sum you are willing to wait.
    • Retry only safe methods, and only when the call did not produce a response you might double-apply. Leave POST idempotency to the application.
    • Run at least two gateway instances. Health-check the gateway, not its downstreams.
    • Keep partner traffic on its own host, audience, and quota.
    • Log route id, status, subject, and latency. Do not log tokens or full request bodies that may contain personal data.
    • When a new internal service appears, a BFF calls it. The public DNS does not gain a new name for that service.

    Common Mistakes and Anti-Patterns

    The god gateway. Screen logic, client-version forks, and partner field renames accumulate in one process the platform team must deploy. Client releases block on that deploy, and a bad route takes every client down. Move experience payloads to BFFs. Leave the edge as auth, route, and limit.

    One public API with view flags. ?view=mobile|web|partner pushes every client’s shape into catalog or into the gateway. Catalog learns about app versions. Add a BFF, or a dedicated partner contract, instead of another flag.

    Identity by a header the client can set. The gateway copies sub into X-User-Id and downstream trusts it. A caller who reaches a BFF directly becomes any learner. Forward Authorization and validate the JWT on each resource server, and block direct access at the network.

    Retrying enrollments at the edge. The gateway Retry filter includes POST, or the BFF retries on any exception after the enrollment service may have committed. The learner is charged or enrolled twice. Retry GETs that failed before a response. Use an idempotency key for POST, specified in lesson 37.

    Shared cache keyed only by course id. The home response includes progress. A cache entry without the learner id serves the previous learner’s resume position. Cache catalog’s public course body if you cache anything. Do not cache the aggregated home response across users.

    Health checks that call the world. The load balancer hits a gateway endpoint that calls catalog. Catalog blips, the balancer drains every gateway, and the site goes dark even though the degrade path would have worked. Probe gateway liveness locally.

    Publishing internal services on the public route table. Path=/catalog/** proxies to catalog so the web app can “just call it.” The internal model becomes a public contract overnight, and authorization that the BFF remembered to do is skipped. Public prefixes are only the front doors you intended.

    Troubleshooting and Design Review

    SymptomLikely causeWhat to checkCorrection
    401 for a token that works in another environmentWrong issuer, stale JWKS, or audience mismatchiss, aud, gateway issuer-uriPoint at the right issuer; fix the audience validator
    Mobile release waits on the platform teamScreen logic in the gatewayRoute filters and recent gateway commitsMove the shape to mobile-bff
    Web and mobile fail togetherShared BFF or shared god gateway, or catalog primary downWhich route ids are failingSeparate deploys; if catalog is down, the fail is correct for both
    Progress outage returns 502 for the whole pageProgress treated as primaryBFF exception mappingDegrade progress; fail only catalog
    Duplicate enrollmentsGateway or BFF retries POSTRetry filter methods, client retriesRemove POST from Retry; add idempotency at enrollment
    p95 far above 500 msSequential fan-out or timeouts longer than the budgetTrace of catalog and progress spansParallelize; shorten read timeouts
    Users see another learner’s progressAggregated response cached without subjectCache keyDrop the cache or key by subject and course
    Partner upload starves the mobile appOne shared quotaRate-limit keysSeparate partner and learner limits (lesson 35)

    Design review for a new public endpoint:

    • Is this a new experience shape, a new route prefix, or a new business capability? The answer picks BFF, gateway, or owning service.
    • Which JWT audience is allowed, and which host?
    • What is primary versus secondary, and what JSON does the client receive in each failure?
    • What are the timeouts at gateway, BFF, and downstream, and do they nest?
    • Can a client reach this process without the gateway?
    • Does any retry list include a non-idempotent method?
    • Who deploys the change, and who is paged when this route returns 502?

    Interview Questions and Answers

    Q1. What belongs on an API gateway, and what belongs on a BFF?

    The gateway terminates public TLS, authenticates, routes by host or path, caps body size, and applies a coarse quota. It is shared and boring. A BFF is the API for one user experience. It aggregates, drops fields that the client does not render, and maps a secondary failure into a documented degraded response. Catalog remains the system of record for course facts. If a change is “the phone shows a download badge,” it ships in the mobile BFF. If a change is “this audience may call this prefix,” it ships in the gateway.

    Q2. Why is a single shared API a problem for web and mobile?

    Web and mobile diverge. A shared payload grows optional fields and view flags so each client can ignore what it does not understand. The team that owns the shared API mediates every screen change. Either client’s urgent fix waits on the other’s release caution. Two BFFs let each client team be specific. The cost is two clients of catalog and progress, which is acceptable when those services expose stable resource APIs and the BFFs own only the composition.

    Q3. How do you validate a JWT on Spring Cloud Gateway without inventing an identity stack?

    Configure spring.security.oauth2.resourceserver.jwt.issuer-uri. Spring Security fetches the OpenID configuration and JWKS and checks signature, issuer, and expiry. Add an OAuth2TokenValidator<Jwt> because that default does not check aud. Wire it with JwtValidators.createDefaultWithIssuer on a NimbusJwtDecoder. Secure the process with SecurityWebFilterChain and oauth2ResourceServer, not with a servlet filter chain. Permit only the health path. A token for the partner audience fails closed on the learner routes.

    Q4. What is a god gateway, and how do you recognize one?

    One deployable contains per-client response rewrites, app-version branches, and partner field mapping, and the platform team’s backlog is full of UI tickets. A defect in any of those rewrites is an outage for every caller. The route table is no longer reviewable as a list of prefixes. Split experience code into BFFs with owning teams. Leave routing, authentication, and coarse limits at the edge. Recognition is organizational as much as technical: if the mobile squad cannot ship a field without the platform squad, the boundary is already wrong.

    Q5. How should the course home behave when progress is down and catalog is up?

    Progress is secondary. The BFF returns 200 with the catalog body and degraded listing progress. The UI renders a placeholder. Catalog is primary: its timeout or 5xx becomes 502, and the BFF does not fabricate a title. A progress 404 is “not started,” not a degradation. Write those three outcomes into the contract so the client does not treat every non-200 as “try the whole page again” and does not treat a missing progress row as an incident.

    Q6. Where do timeouts go, and what happens if they disagree?

    Each downstream call has a connect and read timeout. The BFF’s page budget is at least the parallel max of those timeouts, plus a small local allowance. The gateway’s response-timeout is larger than the page budget and small enough to free resources when a BFF is stuck. If the gateway gives up first, the learner sees 504 while the BFF keeps working and may still mutate something on a POST. Align the numbers and write them in the route review. Parallelize independent reads so the budget is the slowest call, not the sum.

    Q7. Why is forwarding Authorization safer than injecting X-User-Id?

    A header the gateway stamps is safe only if no client can reach the next hop and the gateway overwrites any inbound copy. That invariant breaks the first time a network rule is widened or a new ingress is added. A bearer token can be checked at the BFF and at catalog: signature, issuer, expiry, audience. The subject then comes from a claim the caller cannot edit without invalidating the signature. Lesson 44 adds workload identity for service-to-service calls. Until then, token forwarding is the design that survives a missed firewall rule.

    Q8. How do you stop the gateway from duplicating an enrollment?

    Do not put POST on the gateway Retry filter. Do not let the BFF blindly retry an enrollment call after a timeout, because the service may have committed. GET retries are limited to failures where no response arrived, and to one attempt. The enrollment handler itself deduplicates with an idempotency key, which is the subject of lesson 37. The gateway’s contribution is to not multiply the attempts.

    Q9. How does this layout change when each BFF is its own microservice?

    They already are separate processes in this design. The microservices module makes the operational consequences explicit: separate repositories or pipelines, separate scaling, a contract for catalog that is not “whatever the BFF imported,” and service discovery instead of a hard-coded URI when hosts move. The gateway remains the public choke point for authentication. Discovery does not replace it. Versioning in lesson 40 applies to the partner contract and, with a lighter touch, to BFF-to-catalog calls. A mobile deploy that only changes MobileCourseHome still does not redeploy catalog.

    Q10. What do you put in the access log, and what do you leave out?

    Log the route id, HTTP method, status, latency, token subject, and a correlation id. Those fields debug “who called which front door and what happened.” Leave out the bearer token, refresh tokens, and request bodies that contain an address or a payment reference. A gateway log is a high-volume store. Treating it as a safe place for credentials is how tokens end up in a log vendor. Metrics to page on are 5xx rate by route id, gateway upstream timeouts, and JWT failure rate. A spike in 401 is an issuer or audience incident. A spike in 502 on /mobile only is a mobile BFF incident.

    Hands-On Exercises

    Exercise 1: Web and mobile front doors

    Problem. Specify the public edge and the two course-home BFFs for TechLearningPro. Include the partner host only as a route that must not share the mobile payload.

    Tasks.

    1. Draw the gateway routes: path or host, upstream, prefix stripping, and response timeout.
    2. State the JWT checks, including audience, and how identity reaches catalog.
    3. Define WebCourseHome and MobileCourseHome, including the degraded field.
    4. Mark catalog and progress as primary or secondary and specify status codes.
    5. Set nested timeouts that satisfy a 500 ms p95 budget when dependencies are healthy.
    6. Say which team deploys a new mobile field.
    7. List what the gateway retry filter is allowed to retry.

    Expected deliverables. Route table, two response examples, failure table, timeout budget, ownership notes.

    Acceptance. A progress outage does not invent a course and does not fail a page that catalog served. A mobile field does not require a gateway change. Partner traffic is not served MobileCourseHome. POST is not retried at the edge.

    Exercise 2: Review a gateway that “simplifies” the clients

    The change proxies /catalog/** and /progress/** to internal services, adds ?view=mobile, retries all methods twice, and caches the full JSON by course id. Write the review. Cover the public contract you just created, the retry hazard, the cache key, and the BFF you would put back.

    Rubric.

    Assessment areaWeight
    Edge versus BFF responsibilities25%
    Authentication and trust boundary20%
    Aggregation and partial failure20%
    Timeouts, retries, and isolation between clients20%
    Operability and ownership15%

    Lesson Summary

    The gateway is the public edge: TLS, JWT validation with issuer, signature, expiry, and audience, routing, body limits, and coarse quotas. It is implemented here with Spring Cloud Gateway and Spring Security’s resource server, and it stays thin enough that a route review fits on one page. Each client experience gets a BFF that aggregates selfishly and does not become the system of record. Web and mobile course home share catalog and progress, differ in payload, and degrade when progress fails. The partner API is a separate host, audience, and contract. A god gateway, a view flag on a shared API, header-only identity, and POST retries at the edge are the designs this split exists to retire. When the BFFs deploy as independent services, the microservices module adds discovery, versioned contracts, and zero trust around the same front doors. The edge still authenticates. The experience still belongs to the team that ships the client.

    Further Reading

    Ready to mark this lesson complete?Track your journey across the entire course.