ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 39
Abonament PDF

Designul de API - studiu de caz

Andrada Demian
Developer @ Betfair
PROGRAMARE


În zilele noastre este din ce în ce mai greu ca un serviciu sau produs să devină un succes. Expunerea unui API, acronim pentru Application Programming Interface, poate reprezenta condiția de bază care să asigure acest succes.
Un API definește în mod clar cum interacționează un sistem software sau o componentă cu multitudinea de sisteme existente. Printre avantajele unui API se numără scalabilitatea, flexibilitatea și inovația. Marile companii printre care se numără Microsoft, Google, Facebook sau Twitter nu s-ar afla în momentul de față la acest nivel dacă nu ar fi beneficiat de pe urma expunerii propriilor API-uri.

De ce este dificilă definirea unui API?

Designul de API este unul din lucrurile care pot contribui semnificativ la creșterea valorii unui serviciu. Cu toate acestea, definirea unei noi interfețe reușite este o muncă dificilă. Orice dezvoltator software știe cât de ușor poate codul unui proiect să evolueze într-un cod "spaghetti". API-urile nu sunt nici ele ferite de această degradare. Un API* bine definit trebuie să reflecte obiectivele businessului din care face parte, dar totodată să țină cont și de punctele forte și de limitările organizației cu privire la buget, competențe personale și infrastructură tehnică. Cheia întregului proces este identificarea problemei pe care o rezolvă această nouă interfața. Mai sunt și alte aspecte care trebuie luate în calcul pentru definirea unui API reușit. Un API trebuie să fie:

Folosirea recomandărilor de mai sus asigură înțelegerea API-ului de către consumatorii săi. În consecință integrarea și folosirea lui vor deveni foarte ușoare. Cu cât un API este mai ușor de consumat, cu atât rata de adopție va deveni mai mare și costul dedicat integrării sale mai mic.

Puterea exemplului

 În rândurile următoare vom expune un exemplu concret de API insistând asupra provocărilor de care am avut parte de-a lungul întregului proces de design al API-ului. Următoarele exemple sunt luate dintr-un serviciu web al cărui scop este acela de a administra promoțiile vizibile pe website-ul nostru. Aceste promoții cer clientului să efectueze diverse acțiuni pe site- cum ar fi să se înregistreze, să pună pariuri, să se joace jocuri de tip arcade- toate acestea pentru a putea primi premii preconfigurate.

În organizația noastră serviciile web comunică unele cu altele folosind un protocol RPC. Pentru a putea defini operațiile suportate de fiecare serviciu în parte se folosește o schemă XML numita BSIDL (acronim pentru Betfair Service Interface Definition Language). Specificațiile unui BSIDL descriu operațiile suportate(sau metodele) pe care serviciile le expun plus tipurile de date suportate. Mai jos este un exemplu al unei operații folosite de unul dintre clienții API-ului ( o aplicația internă) pentru a crea o promoție.

<operation name="create">
      <description>Creates a new promotion.</description>
      <parameters>
          <request>
              <parameter name="promotion" type="Promotion" mandatory="true">
                  <description>The promotion to create.</description>
              </parameter>
          </request>
          <response type="ResponseStatus">
              <description>Return ok if it was a successful call and fail otherwise, together with an error code.
        </description>
          </response>
      </parameters>
</operation>  

După cum putem observa în exemplul de mai sus, numele operației este foarte sugestiv, descriind practic ceea ce operația trebuie să facă. De asemenea, este foarte greu de folosit această operație într-un mod incorect, având un singur parametru al cărui nume promoție ( promotion) este destul de sugestiv pentru că exprimă ceea ce vrem sa creăm folosind această operație. Până în acest punct operația este foarte intuitivă.

Principala îngrijorare cu privire la noua interfață a fost cum să modelăm o promoție astfel încât să satisfacă cerințele și în același timp să fie suficient de generică pentru a putea oferi flexibilitate. Cea mai simplă si ușoară soluție ar fi fost să creăm un nou tip pentru fiecare promoție. Această alegere ar fi avut multe dezavantaje printre care se numără și codul duplicat - promoțiile au în comun mai multe caracteristici cum ar fi nume, descriere; rezultatul final ar fi conținut o multitudine de tipuri de promoții pentru fiecare tip nou de promoție care ar fi trebuit adăugat. Nu am fost pe deplin încântați de această variantă așa că după câteva sesiuni de meditat la alte soluții am venit cu ideea de a avea o promoție care are toate câmpurile necesare descrierii unei promoții plus un câmp special numit fulfillmentCriteria. Un fragment reprezentând promoția modelată poate fi văzut mai jos:

   <dataType name="Promotion">
      <description>Encapsulates the promotion entity fields.</description>
      <parameter name="name" type="string">
          <description>The name of the promotion.</description>
      </parameter>
      <parameter name="rank" type="i32">
        <description>Represents the promotion ranking number which is an integer on range[1,1000].
        </description>
      <parameter name="product" type="set(Product)">
          <description>
              Indicates the products for which the promotion is available.
          </description>
      </parameter>
      <parameter name="fulfillmentCriteria" type="FulfillmentCriteria">
         <description>What does the user need to accomplish in order to get the reward.
         </description>
    </parameter>
  </dataType>     

Soluția găsită cu privire la suportarea mai multor tipuri de promoții a fost să folosim compoziția pentru a defini API-ul. Așadar, în loc să creăm un nou tip pentru promoțiile care implică un depozit(ex. depozitează 10 EUR ca să primești un pariu gratis) am creat un nou criteriu numit DepositCriteria care cuprinde cerințele necesare depozitului sumei de 10 EURO. Acest lucru face ca API-ul să fie unul foarte ușor de extins fără să existe schimbări incompatibile cu versiunea precedentă, beneficiind totodată de un mare avantaj, acela de a avea doar un singur tip de promoție.

<dataType name="FulfillmentCriteria">
    <parameter name="compoundCriteria" type="CompoundCriteria"/>
    <parameter name="depositCriteria" type="DepositCriteria"/>
    <parameter name="placeBetCriteria" type="PlaceBetCriteria"/>
    <parameter name="registerCriteria" type="RegisterCriteria"/>
</dataType>

Adăugarea tipului CompoundCriteria a fost necesară pentru cazurile în care o promoție este configurată să suporte mai multe criterii în același timp folosind operatori de tip ȘI,SAU având rolul de a descrie ceea ce un client Betfair trebuie să facă. Mai jos avem un exemplu de arbore de criterii pentru o promoție cu următoarele cerințe.

Înregistrează-te și pune un pariu de 5 EUR pe jocurile de Arcade sau pune un pariu de 5 EUR pe România vs Ungaria pentru a câștiga un pariu gratis.

În ceea ce privește tratarea erorilor se poate observa din primul exemplu faptul că operația de creare a promoției returnează un tip de date numit ResponseStatus în loc să arunce o excepție. Promoția este validată la creare și toate inconsistențele sunt tratate ca erori de business astfel încât clientul este capabil să ia decizii bazate pe aceste erori de business. Am abordat această soluție deoarece ne-am gândit că este mai adecvată pentru consumatorul API-ului. Sugestivă în acest sens este și afirmația lui Martin Fowler: Dacă o eroare este un comportament așteptat, atunci nu ar trebui folosite excepții.

 <dataType name="FulfillmentCriteria">
    <parameter name="compoundCriteria" type="CompoundCriteria"/>
    <parameter name="depositCriteria" type="DepositCriteria"/>
    <parameter name="placeBetCriteria" type="PlaceBetCriteria"/>
    <parameter name="registerCriteria" type="RegisterCriteria"/>
</dataType>

Urmarea bunelor practici prezentate mai sus nu garantează lipsa greșelilor. Noile cerințe apărute au cauzat adăugarea de noi atribute necesare descrierii unei promoții.

 Promotion
  getPromotionName()
  getRank()
  getProduct()
  getDisplayAttributes()
  getJurisdiction()
  getVisibleForGuest()
  getTermsAndConditions()  

Unele atribute au fost grupate în noi tipuri de date cu nume sugestive:

DisplayAttributes
  getTheme()
  getBannerUrl()
  getImageUrl()  

Alte atribute au fost adăugate direct fără să fie grupate pentru că la momentul respectiv nu am găsit un grup potrivit iar adăugarea unui singur atribut într-un grup nu ar fi avut sens. În acest moment există foarte multe atribute în tipul promoție (promotion) și chiar dacă le-am putea introduce într-un grup potrivit- am putea grupa rank și product într-un grup similar cu cel pentru DisplayAttributes- , unul dintre clienții API-ului folosește deja versiunea cu atributele negrupate făcând astfel dificil procesul adăugării unor modificări incompatibile cu versiunea anterioară.

Concluzii

Definirea unui API este o activitate captivantă. Ce am învățat noi din aceasta experiență este că, deși nu toate schimbările de cerință pot fi anticipate din timp, este bine să încerci să prevezi ceea ce va urma, pentru că în acest fel API-ul va deveni mult mai ușor de extins și vor fi și mai puține modificări incompatibile cu versiunea anterioară. 

LANSAREA NUMĂRULUI 83, Târgu-Mureș

Prezentări articole și
Panel: Connected Products

Joi, 30 Mai, ora 17:00
Hotel Plaza, Târgu-Mureș

Înregistrează-te

Facebook Meetup

Conferință

Sponsori

  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • MicroFocus
  • Colors in projects