Ga direct naar de inhoud
DedicatedPHP Contact

Gedeelde PHP-contracten evolueren zonder releases te blokkeren

Gids voor het toepassen van achterwaartse compatibiliteit in PHP bij wijzigingen aan gedeelde componenten, migratie van consumenten en gecontroleerde API-uitfasering.

Redactioneel diagram van gedeelde PHP-contracten met adapters, consumenten en migratiefasen

Een ogenschijnlijk kleine wijziging in een gedeelde PHP-library kan onafhankelijke releases stilleggen. Het hernoemen van een parameter, wijzigen van een standaardwaarde of vervangen van een exception kan een consument breken die vandaag niet wordt gedeployed, in een andere repository staat of de component indirect aanroept. De fout kan optreden tijdens runtime, in een asynchrone taak of bij het deserialiseren van data die vóór de wijziging is gegenereerd.

Achterwaartse compatibiliteit in PHP betekent niet dat elke historische interface behouden moet blijven. Het is een discipline om producenten en consumenten in staat te stellen in verschillende tempo's te evolueren, met een expliciet migratievenster en een verifieerbare uitfasering. Het doel is zowel gedwongen gecoördineerde deployments als de permanente opstapeling van verouderde API's te voorkomen.

Vaststellen wat deel uitmaakt van het interne contract

Vaststellen wat deel uitmaakt van het interne contract — guía visual de DedicatedPHP

Een intern contract is elk gedrag waarvan een andere module afhankelijk is, ook als het niet als externe API is gepubliceerd. Composer-dependencies en PHP-interfaces zijn een zichtbaar onderdeel, maar dekken niet de volledige reikwijdte. Controleer vóór het wijzigen van gedeelde code ten minste deze elementen:

  • Publieke signatures: methodenamen, parameters, volgorde, types, nullability, standaardwaarden en returntype.
  • Semantiek: wat elk argument betekent, welke velden verplicht zijn en welk resultaat bij een concrete conditie wordt verwacht.
  • Fouten: gegooide exceptions, foutcodes, berichten die door clients worden verwerkt en null- of lege resultaten.
  • Gegevens: array-keys, JSON-structuren, queue-berichten, domeinevents, geserialiseerde bestanden en opgeslagen data.
  • Side effects: het verzenden van events, schrijven naar de database, cache-invalidation, HTTP-calls en uitvoeringsvolgorde.
  • Operationeel gedrag: retries, idempotentie, time-outs en de afhandeling van tijdelijke storingen.

Het toevoegen van een veld aan een JSON-response is bijvoorbeeld doorgaans additief, maar is dat niet meer wanneer een consument een gesloten lijst van properties valideert. Evenzo kan een specifiekere exception technisch correct zijn, maar incompatibel als de consument de eerdere exception opvangt om herstel te activeren.

De wijziging classificeren vóór de implementatie wordt geschreven

Classificatie voorkomt dat een ontwerpbeslissing een productie-incident wordt. Het is raadzaam deze in het wijzigingsvoorstel te documenteren, samen met de bekende consumenten en de exitstrategie.

Additieve wijzigingen

Deze voegen een nieuwe mogelijkheid toe zonder het bestaande pad te wijzigen: een nieuwe methode, een optionele parameter met neutrale semantiek, een extra event of een nieuwe versie van een bericht. Ze hebben de voorkeur wanneer consumenten afzonderlijk worden gedeployed. Het nieuwe pad moet naast het oude kunnen bestaan en het eerdere gedrag moet aantoonbaar behouden blijven.

Compatibele wijzigingen met adaptatie

Deze maken het mogelijk het eerdere resultaat te behouden via een vertaallaag. Een oude interface kan bijvoorbeeld delegeren naar een nieuwe service, waarbij argumenten en resultaten worden geconverteerd. Adaptatie is zinvol als deze gelokaliseerd is, een uitfaseringsdatum heeft en geen businessverschil verbergt waarover de consument bewust moet beslissen.

Incompatibele of onzekere wijzigingen

Het verwijderen van een methode, aanscherpen van een type, wijzigen van de betekenis van een status of aanpassen van een opgeslagen formaat is doorgaans incompatibel. Elke wijziging zonder betrouwbare inventaris van consumenten moet ook als onzeker worden behandeld. In beide gevallen volstaat het niet om een nieuwe packageversie te publiceren: er is een transitie, geplande migratie of afzonderlijke contractversie nodig.

Een verifieerbare inventaris van consumenten opbouwen

Baseer de beslissing niet uitsluitend op tekstzoekopdrachten. Een component kan een andere bereiken via een dependency container, configuratie, reflection, events, queues of een HTTP-integratie. De inventaris moet statisch bewijs en representatieve uitvoering combineren.

  1. Controleer dependencies die in Composer zijn gedeclareerd, versieconstraints en repositories die het package installeren.
  2. Zoek naar directe toepassingen van classes, interfaces, methoden, events, configuratie-keys en berichtformaten.
  3. Inspecteer factories, containerdefinities, listeners, commands, cron, workers en infrastructuuradapters.
  4. Identificeer kritieke paden: betalingen, authenticatie, bestellingen, synchronisatie, notificaties en herstelprocessen.
  5. Leg voor elke consument de eigenaar, gebruikte versie, migratieroute en het bewijs vast dat de wijziging is voltooid.

Het publiceren van een library en het deployen van een applicatie zijn verschillende acties. Door een compatibele versie te publiceren kan elke consument updaten wanneer die klaar is; alle consumenten gelijktijdig deployen maakt van een gewone evolutie een fragiele organisatorische afhankelijkheid.

Additieve evolutie en adapters op de juiste grens toepassen

Wanneer een nieuwe vereiste het model wijzigt, introduceer dan eerst een nieuwe mogelijkheid en behoud tijdelijk de bestaande. Een legacy-interface kan delegeren naar de nieuwe implementatie, mits de conversie eenduidig is. Zo kunnen consumenten migreren zonder één enkel venster te hoeven coördineren.

interface LegacyPriceCalculator
{
    public function calculate(int $amount): int;
}

final class LegacyPriceCalculatorAdapter implements LegacyPriceCalculator
{
    public function __construct(private PriceCalculator $calculator) {}

    public function calculate(int $amount): int
    {
        return $this->calculator->calculate(new Money($amount, 'EUR'))->amount();
    }
}

De adapter hoort doorgaans op de grens tussen contracten, niet in de kern van het domein. Het domein moet het huidige model uitdrukken; de vertaling van oude argumenten, sentinel values of historische formaten moet in een specifieke laag blijven. Als het domein voorwaarden voor elke generatie clients behoudt, verspreidt de historische complexiteit zich naar elke toekomstige wijziging.

Dwing geen adapter af wanneer informatie verloren gaat of er een nieuwe businessbeslissing nodig is. Als het oude contract niet de data bevat die voor het nieuwe gedrag nodig is, behoud dan beide contracten tijdens de transitie of vraag de consument expliciet om de aanvullende informatie.

Deprecation omzetten in een beheerde uitfasering

Een API die als verouderd is gemarkeerd zonder alternatief, termijn of eigenaar is geen deprecation: het is schuld zonder opvolging. Een nuttige uitfasering moet een signaal in de code, migratie-instructies, een verwijderingsvoorwaarde en waar mogelijk observatie van het gebruik omvatten.

  • Markeer de legacy-methode of -class met duidelijke documentatie en geef, indien van toepassing, een gecontroleerde waarschuwing met trigger_error(..., E_USER_DEPRECATED).
  • Vermeld het exacte alternatief, inclusief verschillen in semantiek, fouten en standaardwaarden.
  • Definieer een verifieerbare exitvoorwaarde: alle geïnventariseerde repositories zijn gemigreerd, er worden geen calls meer waargenomen of de ondersteuning voor een concrete versie is beëindigd.
  • Wijs een eigenaar aan die de voortgang beoordeelt en de laag verwijdert wanneer aan de voorwaarde is voldaan.

Vermijd het ongecontroleerd geven van waarschuwingen in paden met hoog volume zonder aggregatiestrategie: ruis kan relevante signalen verbergen en de operationele kosten verhogen. Observability moet een concrete vraag beantwoorden: welke consumenten gebruiken nog het eerdere contract en op welk pad?

De transitie testen en de leveringsvolgorde uitvoeren

Unittests van de component bewijzen op zichzelf niet dat consumenten blijven werken. Voeg contracttests toe voor de inputs, outputs en fouten die elke consument nodig heeft. Behoud regressiegevallen voor de oude interface zolang deze wordt ondersteund en test expliciet ontbrekende waarden, eerdere geserialiseerde payloads en verwachte exceptions.

De veilige volgorde volgt doorgaans deze stappen:

  1. Publiceer het nieuwe contract of de additieve implementatie terwijl het eerdere pad behouden blijft.
  2. Werk consumenten onafhankelijk bij en deploy ze, met integratietests waar het risico dat rechtvaardigt.
  3. Observeer fouten, deprecation-waarschuwingen en gebruik van de legacy-interface.
  4. Bevestig de migratie-inventaris en los gedetecteerde indirecte consumenten op.
  5. Verwijder de adapter of het oude contract in een afzonderlijke release, met tests die de afwezigheid ervan bevestigen.

Checklist om de wijziging goed te keuren

Checklist om de wijziging goed te keuren — guía visual de DedicatedPHP
  • Is het getroffen contract gedefinieerd buiten de PHP-signature?
  • Is de wijziging geclassificeerd als additief, aanpasbaar, incompatibel of onzeker?
  • Is er een inventaris van consumenten, inclusief events, data en indirecte paden?
  • Voorkomt de oplossing dat gelijktijdige deployments vereist zijn?
  • Bevindt de adapter zich, indien aanwezig, buiten het domein en is de uitfasering gepland?
  • Zijn het eerdere gedrag, de nieuwe mogelijkheid en verwachte fouten getest?
  • Vermeldt de deprecation een alternatief, uitfaseringsvoorwaarde en eigenaar?
  • Is er een signaal om verborgen dependencies te detecteren vóór de API wordt verwijderd?

De juiste beslissing is niet om compatibiliteit onbeperkt te behouden of totale coördinatie op te leggen. Het is om een transitie met grenzen te ontwerpen: behoud wat nodig is, migreer op basis van bewijs en verwijder historische compatibiliteit wanneer deze geen veiligheid meer biedt.

Wil je deze ideeën toepassen op je project?Laten we uw PHP-platform bespreken.
Bekijk gerelateerde service