fbpx

A čemu služi ovaj gumb? – Krešimir Delija

(Što se dogodilo kada sam kliknuo na ovo nešto i zašto nigdje ne piše čemu to služi?)

 

Dokumentacija, specifikacija, …

Svi oni koji se bave razvojem aplikacija vjerojatno su se susreli s pojmovima “dokumentacija” i “funkcionalna specifikacija softvera”. U slučaju da se time bave duže vrijeme, gotovo je sigurno da su naišli i na zastarjelu funkcionalnu specifikaciju i nepotpunu dokumentaciju. Ovo je, nažalost, uobičajeni problem kod većine projekata za čiji je razvoj potrebno nešto dulje razdoblje (više od 6 mjeseci) te čije se funkcionalnosti s vremenom povećavaju i mijenjaju. Kod ovakvih projekata, dokumentacija kojom se okvirno opisuje što bi sustav trebao postati te funkcionalna specifikacija koja opisuje njegov način rada, obično nastanu na početku.

U ovom trenutku sve je u redu, naručitelj je zadovoljan jer se iz dokumentacije jasno vidi ono što želi dobiti, a u funkcionalnoj se specifikaciji nalazi točan opis očekivanog rezultata. Potom svi spomenuti dokumenti dolaze razvojnom timu koji, nakon što ih prouči, na temelju funkcionalne specifikacije počinje graditi sustav. I dalje sve ide po planu i izgleda da će se projekt realizirati u roku i na opće zadovoljstvo. No, u jednom je trenutku naručitelj poželio manju izmjenu. Uobičajeno za današnje doba kada je “in” biti agilan, analitičar odmah počinje pisati novu funkcionalnu specifikaciju i šalje je razvojnom timu koji, nakon što je primi, počinje s implementacijom. Nekoliko mjeseci i nekoliko desetaka izmjena i dodataka kasnije, sustav je konačno gotov i svi su sretni i zadovoljni – agilni razvoj djeluje, a raspisivanje dodatnih funkcionalnosti u funkcionalnim specifikacijama omogućilo je da se tim u kratkom vremenu prilagodi novim zahtjevima i napravi onakav sustav kakav je naručitelj želio, te je on pušten u produkciju. Ovaj pristup mnogi smatraju dobrim i ispravnim, no njegov je najjači argument ujedno i njegova najveća mana – dodavanjem novih funkcionalnosti kroz nove funkcionalne specifikacije uvodimo dodatnu kompleksnost u samu dokumentaciju, kao i fragmentaciju osnovnih informacija o tome što koji dio sustava zapravo radi.

Ovo može dovesti do toga da nakon nekoliko godina postoji vrlo veliki broj dokumenata koji opisuju određene dijelove sustava, no niti jedan ne opisuje sustav u cijelosti. Situacija je još gora ako određeni dokument u potpunosti ne opisuje niti dio sustava na koji se odnosi jer opisuje samo dio dijela sustava koji je izmijenjen, što je česti slučaj. U konačnici, ovo će znatno otežati razvijanje novih funkcionalnosti, a uvođenje novih članova tima bit će gotovo nemoguće temeljem dokumentacije. Osim toga, upoznavanje funkcionalnosti sustava će se s čitanja funkcionalne specifikacije prebaciti na čitanje softverskog koda da bi se doznalo kako i što koji dio sustava radi.

 

…samo malo, sad ću naći ispravnu verziju…

Ako se možete prepoznati u prethodno opisanoj situaciji, a želite nešto promijeniti, ono što možete napraviti je “oživjeti” svoju dokumentaciju te zamijeniti stari i “mrtvi” način pisanja dokumentacije i funkcionalnih specifikacija pisanjem žive dokumentacije. Živa dokumentacija je dokumentacija koja, za razliku od prethodno opisane, nikada neće zastarjeti niti se protezati kroz nebrojene dokumente kroz koje se potrebno probijati u potrazi za aktualnim informacijama. Ideja koja stoji iza žive dokumentacije je da se programski kod i funkcionalne specifikacija povežu na način da se izmjene u kodu, a koje utječu na funkcionalnost, automatski reflektiraju u dokumentaciji. Ovaj način dokumentiranja rezultira jednim dokumentom pomoću kojega je opisan kompletan sustav zajedno sa svim svojim funkcionalnostima. Ovakvu dokumentaciju zovemo živom dokumentacijom budući da se sa svakom izmjenom u funkcionalnoj specifikaciji i sa svakom novom funkcionalnosti koja se dodaje u sustav i sam dokument mijenja, evoluirajući zajedno sa sustavom koji opisuje.

Pisanje žive dokumentacije na prvi pogled vjerojatno zvuči kao naporan i skoro nemoguć posao koji nikada ne bi uspjeli napraviti sami. Osim toga, nije praktično pretraživati i prepravljati dokument, da ne govorimo o problemu verzioniranja. No, ako odlučite odustati od dokumentiranja funkcionalnosti sustava kroz bezbrojne funkcionalne specifikacije, postoji način da vam dokumentaciju netko, ili nešto, ‘oživi’ te da se taj dokument samostalno mijenja kroz nove verzije sustava.

 

… oprostite dr. Victor F., možete li mi pomoći?

Ipak, neće vam trebati pomoć dragog doktora Victora, i ne trebate se bojati da će se vaša dokumentacija pretvoriti u njegovo poznato čudovište, ali ako ne budete pažljivi, mogla bi se okrenuti protiv vas. Možete koristiti neku od postojećih metoda raspisivanja funkcionalnosti sustava pomoću kojih se može generirati živa dokumentacija te, ovisno o potrebama sustava i odabranom načinu raspisivanja funkcionalnosti, možete odabrati odgovarajući generator. Kako se u današnje doba sustav obično razvija oko njegovog API-a, kao primjer ću opisati kako napraviti živu dokumentaciju nekog API-a koristeći Swagger.

Swagger je framework za opisivanje API-a (REST servisa) običnim jezikom koji bi svi trebali razumjeti, tj. razumljiv je i ljudima i strojevima. Jednostavan je, ali u isto vrijeme i dovoljno složen da može dati uvid u kreiranje osnove sustava naručitelju, analitičarima, kao i developerima. Pritom nije komplicirani nečitljiv onima bez tehničkog znanja, a niti prejednostavan pojedincima koje zanimaju tehnički dijelovi. Za njegovo korištenje postoje dva osnovna načina, top-down i bottom-up, ali, u slučaju potrebe, mogu se i miješati. U top-down načinu najprije se definiraju funkcionalnosti sustava te se raspisuje sve ono što API treba podržavati. Osim toga, svaku se funkcionalnost može dodatno opisati raspisivanjem njenog očekivanog rezultata te primjerom tog rezultata. Dodaje se detaljan opis sustava koji se opisuje, definiraju se svi entiteti s kojima će sustav raditi i načini autorizacije, dodaju se primjeri mogućih odgovora koji se očekuju od API-a, kao i moguće greške. Ovo može biti od velike pomoći developerima jer čak i prije nego li su počeli razvijati sustav, imaju mogućnost testirati ga.

Bottom-up pristup je, kao što mu i ime kaže, potpuna suprotnost prvome. Kod bottom-up pristupa Swagger specifikacija generira se automatski prema postojećem API-u. Čim je generirana, dobiva se website koji sadrži popis svih metoda koje taj API sadrži i primjere poziva prema njima. Može sadržavati i detaljne opise svake od tih metoda. U .net-u, za automatsko generiranje Swagger dokumentacije brine se Swashbuckle. Swashbuckle se nakon dodavanja u projekt konfigurira za kreiranje Swagger definicije. Osim što za vrijeme builda generira Swagger definiciju, Swashbuckle nudi i mogućnost da se metodama dodaju opisi i primjeri koji će biti uključeni u generiranu Swagger definiciju i koji će biti vidljivi u našoj živoj dokumentaciji.

Zasada su prelaskom na živu dokumentaciju usklađeni analitičari, developeri API sustava koji se razvija te developeri aplikacije koja će konzumirati API (naručitelj, korisnici…). Svi su uključeni u raspisivanje dokumentacije. Dodatno je omogućeno da ova dva tima developera mogu neovisno razvijati svoj dio te ne moraju čekati drugi tim da završi kako bi mogli testirati svoj kod.

Sada dolazimo do dijela koji se odnosi na dodavanje nove funkcionalnosti. Tim koji razvija API dobiti će zahtjev za implementaciju nove funkcionalnosti. Čim ju implementira u API, ta funkcionalnost će biti vidljiva i u Swagger dokumentaciji. Na ovaj je način Swagger dokumentacija uvijek ažurna te stalno sadrži opise i primjere korištenja svih funkcionalnosti koje API podržava. Kako se nove funkcionalnosti dodaju, tako se automatski pojavljuju u dokumentaciji. Kako se mijenjaju, te se izmjene vide i u dokumentaciji, a ako se neka ukloni, nestane i iz dokumentacije.

 

Ali kako to zapravo radi?

Koristit ću osnovni primjer pomoću kojega se standardno prezentira Swagger, tj. API za potrebe dućana kućnih ljubimaca. Prema tome standardno radimo Swagger JSON ili yaml file u kojem definiramo „info“ dio s osnovnim podacima vezanim za sustav (Slika 1).

 

 

swagger: "2.0"
info:
description: "This is a sample server Petstore server."
version: "1.0.0"
title: "Swagger Petstore"
termsOfService: "http://swagger.io/terms/"
contact:
email: "apiteam@swagger.io"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "petstore.swagger.io"
basePath: "/v2"

 

 

swagger: “2.0”
info:
description: “This is a sample server Petstore server.”
version: “1.0.0”
title: “Swagger Petstore”
termsOfService: “http://swagger.io/terms/”
contact:
email: “apiteam@swagger.io”
license:
name: “Apache 2.0”
url: “http://www.apache.org/licenses/LICENSE-2.0.html”
host: “petstore.swagger.io”
basePath: “/v2”[/vc_column_text][/vc_column][/vc_row]

 

 

Slika 1

Ovdje je definirano kako nam se zove sustav, njegov opis, gdje se može vidjeti uvjete njegova korištenja, informacije o licencama te verziju i informacije za kontakt. Nakon toga dodajemo dio sa „tagovima“ koji služe za razdvajanje određene logičke cjeline i njihovo grupiranje u smislenu strukturu (Slika 2).

 

tags:
-name: “pet”
description: “Everything about your Pets”
externalDocs:
description: “Find out more”
url: http://swagger.io
-name: “store”
description: “Access to Petstore orders”
-name: “user”
description: “Operations about user”
externalDocs:
description: “Find out more about our store”
url: “http://swagger.io”

Slika 2

Sljedeći dio Swagger definicije su putanje. Putanje označavaju rutu na kojoj se nalazi određena funkcija te http glagol koji ona prihvaća. U ovom djelu definiramo koja će biti putanja na kojoj se određena funkcija nalazi (osnovna adresa sustava + ruta funkcije) te dodajemo definicije za svaki http glagol koji ona podržava. Pri definiranju http glagola, možemo mu dodati neki tag prema kojemu će kasnije u dokumentaciji biti grupiran te kratki i dugi opis funkcionalnosti vezane za taj glagol koji će biti korišteni pri generiranju dokumentacije. Nakon toga definiramo koju metodu API-a ta funkcija poziva, koji tip informacija prima i vraća, te potrebne parametre za pozivanje. Dodatno se može definirati primjere odgovora za svaki http status te način autorizacije (Slika 3).

 

paths:
/pet:
post:
tags:
-“pet”
summary: “Add a new pet to the store”
description: “”
operationId: “addPet”
consumes:
– “application/json”
– “application/xml”
produces:
– “application/xml”
– “application/json”
parameters:
– in : “body”
name: “body”
description: “Pet object that needs to be added to the store”
required: true
schema:
$ref: “#/definitions/Pet”
responses:
405:
description: “Invalid input”
security:
– petstore_auth:
– “write:pets”
– “read:pets”

Slika 3

Slijedi dio definicije o načinu autorizacije korisnika. U ovom dijelu definiramo nazive za načine autorizacije koji će se koristiti kroz definiciju. Za svaki se način autorizacije definira tip autorizacije (Slika 4).

 

securityDefinitions:
petstore_auth:
type: “oauth2”
authorizationUrl: “http://petstore.swagger.io/oauth/dialog”
flow: “implicit”
scopes:
write: pets: “modify pets in your account”
read: pets: “read your pets”
api_key:
type: “apiKey”
name: “api_key”
in: “header”

Slika 4

Zadnji, ali možda i najvažniji dio u specificiranju, su definicije. Ovo je dio u kojem se definiraju svi entiteti s kojima sustav radi. Svakom entitetu se, osim samog opisa, dodjeljuju i svi njegovi dijelovi za koje se, opet, pojedinačno dodaje naziv, tip i opis. Dodatno, osim uobičajenih tipova kao što su riječi i brojevi (string, int…), dijelovi entiteta mogu biti i „enum-i“, tj. setovi fiksno zadanih vrijednosti. Osim toga, mogu imati definirane i posebne načine formatiranja te unaprijed definirane vrijednosti. U slučaju da sve navedeno nije dovoljno za definiranje određenog entiteta, dio jednog entiteta može se dodatno definirati drugim entitetom te tako olakšati ili zakomplicirati život ostatku ljudi koji rade na sustavu (Slika 5).


definitions:
Order:
type: "object"
properties:
id:
type: "integer"
format: "int64"
petId:
type: "integer"
format: "int64"
quantity:
type: "integer"
format: "int32"
shipDate:
type: "string"
format: "date-time"
status:
type: "string"
description: "Order Status"
enum:
-"placed"
- "approved"
- "delivered"
complete:
type: "boolean"
default: false
xml:
name: "Order"

Slika 5

Pa ovo nije “programski kod”? Ja sam mislio da će se to programirati

Slika 6

 

Kao što možete vidjeti, ne može biti jednostavnije, ali sadrži unaprijed gotove kontrolere s pripremljenim pozivima svih metoda koje smo definirali te klase koje su napravljene prema entitetima koje smo opisali u definicijama. Važno je napomenuti da ovaj template ima reference na Swashbuckle. SwaggerGen i Swashbuckle. SwaggerUI. Te reference omogućavaju da kroz anotacije i komentare u kodu dodajemo sadržaj u dokumentaciju (Slika 7).

 

///

/// Delete user
///
/// This can only be done by the logged in user!
///The name that needs to be deleted
[HttpDelete]
[Route(“/v2/user/{username}”)]
[SwaggerOperation(“DeleteUser”)]
[SwaggerResponse(HttpStatusCode.BadRequest, “The name that needs to be deleted”)]
[SwaggerResponse(HttpStatusCode.NotFound, “User not found”)]
public virtual void DeleteUser([FromRoute] string username) {
throw new NotImplementedException();
}

Slika 7

Na slici 7 je prikazan jedan takav primjer. U komentaru smo definirali koji parametar metoda prima zajedno s njegovim opisom te dodali anotacije za očekivane odgovore zajedno s njihovim opisima. Dodali smo i napomenu da ovu akciju može raditi samo prijavljeni korisnik te da je to DeleteUser operacija. Nakon sljedećeg pokretanja sustava, dokumentacija je ponovno generirana koristeći ove anotacije (Slika 8).

Slika 8

U trenutku kada trebamo dodati novu funkcionalnost u sustav, samo ćemo je implementirati u API te će se kod builda ona automatski dodati i u dokumentaciju. Recimo da za entitet User želimo dodati adresu, i to ne kao običan tekst, nego kao drugi entitet. U kodu bi to napravili tako da dodamo novu klasu Adress, te da u klasu User dodamo novi dio koji bi bio tipa Adress. Na ovaj jednostavan način, kreiranjem nove klase i njezinim dodavanjem u klasu User, proširili smo naš model tako da uključuje i adresu (Slika 9).

///

/// Users adress
///
[DataMember(Name = “userAdress”)]
public Adress Adress {
get;
set;
}

Slika 9

Ovo je nova funkcionalnost koja nije bila definirana u početku te se ne nalazi u Swagger definiciji. Kada smo ju dodali u model, i prilagodili sve u aplikaciji da sustav stvarno koristi i adresu kod builda, Swashbuckle će to prepoznati i prilagoditi dokumentaciju na način da se odmah vidi da User od sada ima i adresu (Slika 10).

Slika 10

I to je to? Oa zašto ne koristimo?

Da, to je to. Jednom definirana ‘živa’ dokumentacija korištenjem alata za generiranje dokumentacije i s malo truda može vas osloboditi zamornog pretraživanja po starim dokumentima te vam omogućiti da uvijek imate svježu dokumentaciju. Više vam ne zvuči tako teško i nemoguće? Nadam se da je tako!