---
title: "How To Build A Drupal Member Portal On An AMS You Cannot Replace"
url: https://www.axelerant.com/blog/drupal-member-portal-ams-architecture
published: 2026-07-23T10:30:00Z
author: "Bassam Ismail, Director of Digital Engineering"
source: Axelerant Thinking
---

# How To Build A Drupal Member Portal On An AMS You Cannot Replace

> A production architecture for Drupal member portals backed by an external AMS: SAML identity, structured queries, circuit breakers, BigPipe, and cache design.

The scenario: a member-based organization runs its memberships, certifications, and payments in an association management system it will not replace, and wants the member experience on Drupal. The three ways this goes wrong are predictable. Per-member pages defeat the page cache and the site crawls. The AMS's APIs answer in seconds, not milliseconds, and every portal page inherits that latency. And identity lives in two places until, inevitably, it disagrees with itself.

We shipped a Drupal 11.1 build against exactly this shape recently. What follows is the architecture that held up, with the decisions in the order we made them.

## The Problem At The Platform's Level

Drupal's render pipeline is built around cacheability. An anonymous page is cheap because everyone gets the same response. A member portal inverts that: nearly everything interesting varies per user, and the source of that variation is not even in Drupal's database. It is across an HTTP boundary in the AMS. Naive implementations either mark everything uncacheable, which melts under load, or cache per-user data carelessly, which is how one member sees another member's certificates. Both failure modes are architectural, not bugs.

## Why The Naive Approaches Fail

Direct API calls in the render path. Every block that calls the AMS synchronously adds that latency to time-to-first-byte. Five blocks, five round trips, one unusable page.

Setting max-age: 0 everywhere. Marking per-user content uncacheable is correct and insufficient. Without a strategy for what remains cacheable, the whole page shell becomes dynamic and you have rebuilt a 2010-era CMS with more YAML.

Trusting the upstream to be up. An AMS has maintenance windows, rate limits, and bad days. If your portal's availability equals the AMS's availability, you shipped their SLA as your own.

## The Approach, In Order

1. Fix identity ownership first. The AMS is the SAML 2.0 identity provider; Drupal is the service provider via the samlauth contrib module. The member's ID in the AMS is stored on the Drupal account, and the Drupal username is the AMS ID itself, synced on every login along with role attributes that map to Drupal roles through configuration. Drupal never authenticates independently and never becomes a second identity store.

1. Declare the source-of-truth split. The AMS owns identity, certifications, registrations, invoices, payments. Drupal owns editorial content, course catalog pages, events. Algolia owns the derived search index. Every data domain has exactly one authoritative home, and Drupal stores only the linkage.

1. Wrap the AMS in one client, then defend it. All AMS traffic goes through a single injected API client service. No module talks to the AMS directly.

1. Design the cache before the features. Redis for render, default, and page caches; tag-based invalidation; a CDN in front for anonymous traffic.

1. Make per-user content a streaming concern. BigPipe plus lazy builders, so the cacheable shell ships immediately.

## The Solution In Depth

The API client and its structured query layer. The client is a bearer-token authenticated REST wrapper registered as a service and injected everywhere. The performance unlock was the AMS's structured query layer: instead of stitching multiple raw REST reads per page, predefined server-side queries return purpose-shaped result sets in one call. Repositories, one per domain (certificates, current registrations, invoices, company rosters), own these queries. Controllers and blocks stay thin and never see HTTP.

The circuit breaker. A dedicated breaker service wraps the client. After a configurable number of consecutive failures it opens, and every subsequent call returns cached or empty data immediately instead of waiting on a dying upstream. The portal degrades to quiet empty states rather than timeouts, and the site's availability decouples from the AMS's. Reset is a one-line Drush eval. This was the single highest-value resilience decision in the build.

BigPipe and the lazy-builder discipline. Every per-member block follows the same pattern: the block render array is a #lazy_builder placeholder keyed by user ID, the page shell caches normally, and BigPipe streams the placeholder resolutions in a sub-request that pulls from the AMS, Redis, or the database. Two rules made this safe. The lazy builder loads the user entity by the explicit ID it was handed, never by sniffing the current request, so the data flow is auditable. And every per-user fragment re-validates identity at render time, defense in depth against any upstream cache layer misbehaving with PII.

Cache invalidation as a first-class design. Repository results cache in Redis keyed per member ID. Invalidation is tag-based: a certificate publish event clears the certificate tags, a registration update clears registration tags, a user save clears that user's tags, and the member's active-role change carries its own cache tag so role switches re-render menus and blocks instantly. The honest note: one upstream event still requires manual invalidation because the AMS webhook integration is pending. It is on the list.

Acceptance criteria that made it real:

- Anonymous pages served from CDN or page cache with no AMS calls in the critical path

- Authenticated shell TTFB independent of AMS latency; member data streams via BigPipe

- AMS outage produces empty states, never 5xx responses, with the breaker open

- No per-user fragment renders without an identity re-check

- All configuration exported to code with per-environment splits; no hand-installed modules outside Composer

The supporting cast. Search runs on Algolia through Search API with a custom datasource indexing both Drupal content and the AMS product catalog. File delivery for gated course material uses S3 pre-signed URLs minted per click through a controller, never embedded in cacheable HTML, because a pre-signed URL in a cached page is a leaked credential with a timer.

## The Honest Edge

None of this makes the AMS faster; it makes its slowness survivable. The structured query layer depends on queries defined inside the AMS, which means part of your performance tuning lives in a vendor tool your Drupal team does not control. And the webhook gap above means one cache path is still manually operated. In-flight is in-flight.

Locating this on the loop takes one sentence: the portal is the experience layer, the AMS integration is the data layer underneath it, and the build order ran that way deliberately, because you cannot render what you cannot reliably fetch.

If you are scoping a build like this, the architecture above is the conversation to have before sprint one. It is the kind of work our [Drupal practice](https://www.axelerant.com/platforms/drupal) has been doing since 2005.

---

Read on the web: https://www.axelerant.com/blog/drupal-member-portal-ams-architecture
