Praktický návod
Jak provozovat více e-shopů z jedné administrace
Sdílej společná data a workflow, ale každou operaci prováděj v explicitním scope konkrétního obchodu.
Nejdřív stručně
Jedna aplikace neznamená jednu konfiguraci
Multistore může sdílet katalog, zákazníky nebo objednávkové workflow, zatímco doména, ceny, obsah, sklad, platby a oprávnění se liší podle obchodu.
Tenant nebo shop ID proto není volitelný filtr přidaný na konci dotazu. Je součástí identity, unikátních constraintů, cache, událostí, audit logu a každého background jobu.
Připrav si
Rozhodni, co se sdílí a co se odděluje
Pro každou entitu vytvoř matici vlastnictví. Nejasné „většinou společné“ vede k nečitelným override pravidlům.
- Seznam obchodů, tenantů, domén, právních entit a administrátorů s jejich oprávněními.
- Matici globální, tenant-specific a overridable pro produkty, ceny, obsah, zákazníky, sklady a objednávky.
- Požadavky na izolaci dat, audit a případné regulatorní oddělení tenantů.
- Strategii provozu: společná databáze se scope, samostatná schémata nebo databáze a důvod zvolené hranice.
Kroky 1 až 3
Udělej shop context povinný
Aktivní obchod se určí jednou na vstupu a dál se předává jako explicitní hodnota, ne jako skrytá globální proměnná.
1. Modeluj sdílení a override bez magie
- Globální produkt může držet SKU a technická data; shop listing viditelnost, lokální název, kategorii a ceník.
- Override ulož explicitně s jasným fallbackem na globální hodnotu. Null nesmí současně znamenat zdědit, skrýt i smazat.
- Objednávka, platba a doklad vždy patří jednomu shopu a nesmí měnit tenant. Sdíleného zákazníka váž na shop-specific souhlasy a historii.
- Unikátní constrainty navrhuj se shop_id tam, kde je hodnota unikátní jen v rámci obchodu, například slug nebo číslo lokální řady.
UNIQUE (shop_id, slug) Oficiální PostgreSQL dokumentace ke constraintům 2. Izoluj dotazy, oprávnění a asynchronní práci
- Repository vyžaduje ShopId nebo tenant context a každý administrátorský požadavek ověří oprávnění pro konkrétní shop.
- Cache klíče, soubory, search indexy, exporty a message payloady obsahují shop_id. Chybějící scope odmítni, neodvozuj z poslední relace.
- Worker načte shop context ze zprávy a znovu ověří, že cílová data patří stejnému obchodu. Webový session context do fronty nepřenášej.
- PostgreSQL Row-Level Security může být další obranná vrstva, ale nenahrazuje správný aplikační návrh ani testy.
handler(message.shopId, message.entityId) Oficiální PostgreSQL dokumentace k Row Security 3. Mapuj doménu na známý obchod
- Host mapuj přes allowlist aktivních domén na shop ID. Neznámou doménu odmítni a nevytvářej z ní tenant dynamicky.
- Canonical URL, odesílatel e-mailu, platební klíče a branding vybírej z ověřeného shop contextu, ne přímo z nedůvěryhodné Host hlavičky.
- Administrace umožní vědomě přepnout aktivní shop a stále ho viditelně zobrazuje. Hromadná akce explicitně ukáže všechny dotčené obchody.
- Konfiguraci verzuj a audituj: kdo změnil doménu, ceník, sklad nebo platební účet a kdy se změna projevila.
verified host → domain registry → shop_id Oficiální Symfony Security dokumentace Krok 4
Testuj především únik mezi obchody
Happy path jednoho shopu je jednoduchý. Kritická je záporná kontrola, že jiný shop data neuvidí ani nezmění.
-
Použij stejné ID v různých shopech
Ověř repository, URL, cache, export i worker. Požadavek shopu A nikdy nesmí vrátit objekt shopu B.
php bin/phpunit --filter TenantIsolation -
Změň globální a lokální hodnotu
Globální změna se projeví jen tam, kde není explicitní override; jeho odstranění obnoví zděděnou hodnotu.
-
Pošli zprávu se špatným shop ID
Worker ji bezpečně odmítne a zaloguje konflikt bez změny cílového objektu.
Když to zlobí
Nejčastější chyby
Produkt z jiného shopu se objevil v administraci
Některý dotaz, cache klíč nebo search index nemá tenant scope. Udělej shop context povinným argumentem a přidej regresní izolační test.
Override nelze vrátit na globální hodnotu
Model nerozlišuje zdědit, explicitní null a vlastní hodnotu. Zaveď jasný stav override a samostatnou operaci reset.
Worker používá konfiguraci posledního webového requestu
Tenant context je globální nebo session-based. Zpráva musí nést shop ID a worker z něj načte nový izolovaný context.
Neznámý Host zobrazí výchozí obchod
Fallback je bezpečnostní i SEO riziko. Doménu porovnej s allowlistem a neznámý host odmítni.
Hotovo
Jedna administrace sdílí práci, ne data omylem.
Shop context je teď povinný v dotazech, oprávněních, cache i zprávách. Sdílení a lokální override mají jasná pravidla a každý zásah zůstává auditovatelný.