PrefaceSection 1: Establishing an Architectural VisionChapter 1: Architecting for InnovationContinuously delivering business valueBy the skin of our teethThrough high-velocity teamworkTaking control of lead timeRisk mitigationDecision makingSoftware Development Life Cycle methodologyHardware provisioningSoftware deploymentSoftware structureTesting and confidenceDependencies and inter-team communicationDissecting integration stylesBatch integrationSpaghetti integrationReal-time integrationEnterprise application integrationShared databaseService-oriented architectureMicroservicesEnabling autonomous teams with autonomous servicesAutonomous services – creating bulkheadsEvent-first – valuing factsServerless-first – creating knowledgeData life cycle – fighting data gravityMicro frontends – equalizing tiersObservability – optimizing everythingOrganic evolution – embracing changeSummaryChapter 2: Defining Boundaries and Letting GoBuilding on SOLID principlesSingle Responsibility PrincipleOpen-Closed PrincipleLiskov Substitution PrincipleInterface Segregation PrincipleDependency Inversion PrincipleThinking about events firstEvent stormingVerbs versus nounsFacts versus ephemeral messagesContracts versus notificationsReact and evolvePowered by an event hubDividing a system into autonomous subsystemsBy actorBy business unitBy business capabilityBy data life cycleBy legacy systemCreating subsystem bulkheadsDecomposing a subsystem into autonomous servicesContext diagramMicro frontendEvent hubAutonomous service patternsDissecting an autonomous serviceRepositoryCI/CD pipeline and GitOpsTestsStackPersistenceTrilateral APIFunctionsMicro-appShared librariesGoverning without impedingLeveraging observabilityFacilitating a culture of robustnessAudit continuouslySecuring the perimeterElevating TestOpsAutomating account creationSummarySection 2: Dissecting the Software Architecture PatternsChapter 3: Taming the Presentation TierZigzagging through timeClient-side versus server-side renderingBuild-time versus runtime renderingWeb versus mobileBreaking up the frontend monolithBy subsystemBy user activityBy device typeBy versionDissecting micro frontendsThe main appMicro-appMicro-app activationMount pointsManifest deployerInter-application communicationDesigning for offline-firstTransparencyLocal cacheLive updatesRegional failoverSecuring the user experienceOpenID ConnectConditional renderingProtected routesPassing the JWT to BFF servicesObserving real user activityRUMSyntheticsSummaryChapter 4: Trusting Facts and Eventual ConsistencyLiving in an eventually consistent worldStagingConsistencyConcurrency and partitionsOrder tolerance and idempotenceParallelismPublishing to an event hubEvent busDomain eventsRouting and channel topologyDissecting the Event Sourcing patternSystemwide event sourcingEvent lakeEvent streamsMicro event storesProcessing event streamsBatch sizeFunctional reactive programmingUnit of workFiltering and multiplexingMappingConnectorsDesigning for failureBackpressure and rate limitingPoison eventsFault eventsResubmissionOptimizing throughputAsynchronous non-blocking I/OPipelines and multiplexingShardingBatching and groupingObserving throughputWork metricsIterator ageAccounting for regional failoverProtracted eventual consistencyRegional messaging channelsSummaryChapter 5: Turning the Cloud into the DatabaseEscaping data's gravityCompeting demandsInsufficient capacityIntractable volumesEmbracing data life cycleCreate phaseUse phaseAnalyze phaseArchive phaseTurning the database inside outThe transaction logDerived dataDissecting the CQRS patternSystemwide CQRSMaterialized viewsInbound bulkheadsLive cacheCapacity per reader, per queryKeeping data leanProjectionsTime to liveImplementing idempotence and order toleranceDeterministic identifiersInverse optimistic lockingImmutable event triggersModeling data for operational performanceNodes, edges, and aggregatesSharding and partition keysSingle table design examplesLeveraging change data captureDatabase-first event sourcingSoft deletesLatchingReplicating across regionsMulti-master replicationRound-robin replicationRegional failover, protracted eventual consistency, and order toleranceObserving resource metricsCapacityThrottling and errorsPerformanceRedacting sensitive dataEnvelope encryptionGeneral Data Protection Regulation (GDPR)SummaryChapter 6: A Best Friend for the FrontendFocusing on user activitiesA BFF service is responsible for a single user activityA BFF service is owned by the frontend teamDecoupled, autonomous, and resilientDissecting the Backend for Frontend (BFF) patternDatastoreAPI GatewayQuery and command functionsListener functionTrigger functionModels and connectorsChoosing between GraphQL and RESTRESTGraphQLImplementing different kinds of BFF servicesTask BFF servicesSearch BFF servicesAction BFF servicesDashboard BFF servicesReporting BFF servicesArchive BFF servicesSecuring a BFF in depthThe perimeterFederated identityIn transitJWT authorizerJWT assertionJWT filterLast modified byLeast privilegeAt restLeveraging multiple regionsLatency-based routingRegional health checksRegional failoverObserving BFF metricsWork metricsThrottling and concurrency limitsOptimizing BFF performanceFunction memory allocationCold startsTimeouts and retriescache-controlSummaryChapter 7: Bridging Intersystem GapsCreating an anti-corruption layerDissecting the External Service Gateway patternConnectivitySemantic transformationAction versus reactionEgressIngressPackagingSeparate cloud accountsIntegrating with third-party systemsEgress – API callIngress – webhookAsynchronous request responseIntegrating with other subsystemsEgress – upstream subsystemIngress – downstream subsystemIntegrating across cloud providersIntegrating with legacy systemsIngress – Change Data CaptureEgress – Direct SQLEgress – circuit breakerIngress – relayEgress – relayProviding an external API and SPIIngress – eventIngress – commandEgress – webhookEgress – queryTackling common data challengesIdempotenceEnriching dataLatching and cross-referencingSlow data resyncManaging shared secretsSecuring secretsUsing API keysAddressing multi-regional differencesEgress routing and failoverIngress routing and failoverSummaryChapter 8: Reacting to Events with More EventsPromoting inter-service collaborationDissecting the Control Service patterncollectcorrelatecollateevaluateemitexpireOrchestrating business processesEntry and exit eventsParallel executionEmploying the Saga patternCompensating transactionsAbort eventsCalculating event-sourcing snapshotsWhat is ACID 2.0?Snapshot eventsImplementing CEP logicDecision tablesMissing eventsLeveraging ML for control flowModelsPredictionsImplementing multi-regional cron jobsJob records and eventsReplication and idempotenceSummarySection 3: Putting Everything in MotionChapter 9: Choreographing Deployment and DeliveryOptimizing testing for continuous deploymentContinuous discoveryContinuous testingFocusing on risk mitigationSmall batch sizeDecoupling deployment from releaseFeature flagsFail forward fastAchieving zero-downtime deploymentsThe Robustness principleBetween the frontend and its backendBetween producers and consumersBetween the backend and its data storePlanning at multiple levelsExperimentsStory backlogTask roadmapsTurning the crankTask branch workflowContinuous integration pipelineContinuous deployment pipelineFeature flippingSummaryChapter 10: Don't Delay, Start ExperimentingGaining trust and changing cultureEstablishing a visionBuilding momentumConstructing an architectural runwaySeed and splitFunding products, not projectsArchitecture-drivenTeam capacity-drivenDissecting the Strangler patternEvent-first migrationMicro frontend – headless modeRetirementAddressing event-first concernsSystem of record versus source of truthDuplicate data is goodAvoid false reusePoly everythingPolyglot programmingPolyglot persistencePolycloudSummaryOther Books You May Enjoy