VariousArtists
Meeskond
Liikmed:
- Ruudi Vinter - arendaja
- Madis Roosioks - arendaja, projektijuht
- Pavel Fleišer - arendaja
Analüüs
Kirjeldus
Me loome raamatute laenutamise teenuse, kus raamatukogude asemel on raamatute kohale toomise ja laenutamise punktid a la pakiautomaadid.
Kasutajad
- Admin
- Tavakasutaja
Must have
- Logimine iga kasutaja ja tegevuse kohta.
Tavakasutaja
- Tavakasutaja saab ennast raamatukogu kasutajaks registreerida.
- Tavakasutaja näeb oma käes olevaid raamatuid.
- Tavakasutaja näeb oma tähtaegasid.
- Tavakasutaja saab otsida raamatute seast.
- Tavakasutaja saab raamatukogule soovitada raamatuid, mida ta tahaks laenutada, mida hetkel raamatukogus üldse pole.
- Tavakasutaja saab raamatu broneerida raamatu ette määratud kuupäevani.
- Tavakasutaja ja admini sisselogimised.
Admin
- Admin saab raamatuid lisada süsteemi.
- Admin saab kustutada tavakasutaja.
- Admin saab tühistada broneeringu
- Admin saab registreerida uusi admin kasutajaid
- Admin saab lisada uusi kategooriaid.
- Admin saab eemaldada kategooriaid.
- Admin saab lisada uusi autoreid.
- Admin saab eemaldada autoreid
Nice to have
- Raamatute transpordi haldussüsteem
- Tavakasutaja saab kommenteerida raamatu ning anda raamatule hinnangu.
- Admin saab kustutada kommentaari, mis on ebasobiva sisuga.
XML
<?xml version="1.0" encoding="utf-8" ?>
<books>
<book id="1" published="29.05.2018" type="hardcover" pages="254" deleted="false">
<title><![CDATA[Calypso]]></title>
<authors>
<author id="1">
<firstname>
<![CDATA[David]]>
</firstname>
<lastname>
<![CDATA[Sedaris]]>
</lastname>
</author>
</authors>
<categories>
<category id="1" name="fiction"/>
<category id="2" name="humor"/>
<category id="3" name="entertainment"/>
</categories>
</book>
<book id="2" published="05.06.2018" type="audiobook" deleted="true">
<title><![CDATA[Florida]]></title>
<authors>
<author id="2" birthdate="23.07.1978">
<firstname>
<![CDATA[Lauren]]>
</firstname>
<lastname>
<![CDATA[Groff]]>
</lastname>
</author>
</authors>
<categories>
<category id="1" name="fiction"/>
<category id="4" name="short stories"/>
</categories>
</book>
<book id="2" published="20.10.2015" type="audiobook" deleted="false">
<title><![CDATA[Illuminae]]></title>
<authors>
<author id="3" birthdate="21.11.1958">
<firstname>
<![CDATA[Amie]]>
</firstname>
<lastname>
<![CDATA[Kaufman]]>
</lastname>
</author>
<author id="4" birthdate="13.09.1973">
<firstname>
<![CDATA[Jay]]>
</firstname>
<lastname>
<![CDATA[Kristoff]]>
</lastname>
</author>
</authors>
<categories>
<category id="1" name="fiction"/>
<category id="5" name="romance"/>
</categories>
</book>
</books>
XSD
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="books">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="book">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string" />
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="author">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string" />
<xs:element name="lastname" type="xs:string" />
</xs:sequence>
<xs:attribute name="id" type="xs:int" use="required" />
<xs:attribute name="birthdate" type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="categories">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="category">
<xs:complexType>
<xs:attribute name="id" type="xs:int" use="required" />
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:int" use="required" />
<xs:attribute name="published" type="xs:string" use="required" />
<xs:attribute name="type" type="xs:string" use="required" />
<xs:attribute name="pages" type="xs:int" use="optional" />
<xs:attribute name="deleted" type="xs:boolean" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
XSLT (HTML)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/books">
<html>
<head>
<title>Books list</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous"/>
</head>
<body>
<h2>Books list</h2>
<table class="table">
<th>Title</th>
<th>Type</th>
<th>Authors data</th>
<th>Categories data</th>
<xsl:for-each select="book">
<xsl:if test="@deleted = 'false'">
<tr>
<td>
<xsl:value-of select="title"/>
</td>
<td>
<xsl:value-of select="@type"/>
<xsl:if test="@type = 'hardcover'">
:
<xsl:value-of select="@pages"/> Pages
</xsl:if>
</td>
<td>
<xsl:for-each select="authors/author">
<div>
<xsl:value-of select="firstname"/>
<xsl:value-of select="lastname"/>
<xsl:if test="@birthdate">:
<xsl:value-of select="@birthdate"/>
</xsl:if>
</div>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="categories/category">
<div>
<xsl:value-of select="@name"/>
</div>
</xsl:for-each>
</td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
XSLT (XML)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/books">
<Books>
<xsl:for-each select="book">
<xsl:if test="@deleted = 'true'">
<Book>
<Title>
<xsl:value-of select="title"/>
</Title>
<Type>
<xsl:value-of select="@type"/>
</Type>
<Authors>
<xsl:for-each select="authors/author">
<Author>
<Name>
<xsl:value-of select="firstname"/><xsl:value-of select="lastname"/>
</Name>
<BirthDate>
<xsl:value-of select="@birthdate"/>
</BirthDate>
</Author>
</xsl:for-each>
</Authors>
<Categories>
<xsl:for-each select="categories/category">
<Category>
<Name>
<xsl:value-of select="@name"/>
</Name>
</Category>
</xsl:for-each>
</Categories>
</Book>
</xsl:if>
</xsl:for-each>
</Books>
</xsl:template>
</xsl:stylesheet>
Tegevuste logi
- 22.03.2018 Meeskonna moodustamine.
- 27.03.2018 Meeskonna esimene kohtumine.
- 01.04.2018 Analüüsi esitamine.
- 29.03.2018 Esialgne andmemudel (Ruudi Vinter)
- 01.04.2018 Andmemudeli tõlkimine inglise keelde (Madis Roosioks)
- 19.04.2018 Andmemudeli edasiarendus (Ruudi Vinter)
- 01.05.2018 Raamatu tüüpide kontrolleri implementatsioon ja BookInBookType olemi loomine (Ruudi Vinter)
Retsensioonid
XML retsensioon meeskonnale Garagefy
XML on nõuetele vastav, kuna XML sisaldab vähemalt 4 loogilist dimensiooni ning on ka olemas vähemalt 3 dimensioonil atribuut, mis on enamat kui id. XML’i struktuur on korrekne ning ka XML-validator ei tuvastanud ühtegi viga.
XML sisaldab broneeringuid ning iga broneering sisaldab broneerimiskuupäeva ning ka masina omaniku ja masina andmeid.
XML schema on korrekne kuna ei sisalda andmetüüpe väikseid andmetüüpe (näiteks byte), mille mahutavus ei oleks piisavalt suur.
XSLT (HTML) transformatsioon on korrektne, kuna ei tuvastanud vigu, mille tõttu html oleks ebakorrektne. XSLT (XML) transformatsioon tundub olevat ka korrektne.
Klientrakenduse retsensioon meeskonnale Garagefy
Garagefy projekti klientrakendus töötab brauserites ja kasutab Bootstrapi. Näeb klientrakendus kasutajasõbralik välja. Avalehele sattudes kasutaja näeb vaid tervituspilti, mis ei ole responsiivne. Hea alternatiiviks võiks olla tekstitervitus ja logimisvorm.
Esilehel ei olnud tõenäoliselt hea mõte firma logo ja lööklause pildina panna, kuna akna suuruse muutmisel lõigatakse tekst pooleks, mis ei näe just professionaalne välja. Oleks võinud kasutada mingit suurusetundlikku lahendust. Ilus logo, mis juba loodud oleks võidud panna näiteks navigatsiooniribale, kus praegu on lihtsalt tekstina kirjutatud firma nimi.
Rakenduses kuvatud alalehed on liigitatud loogiliselt ning arusaadavalt ehk enne peale vajutamist on juba aimdus, mida seal kuvatakse. Menüü on resposiivne ning väga loogiline ja mugav kasutamises.
Registreerimiseks peab kasutaja klikkima Register nuppule, mis asub ülemmenüüs. Registreerimiseks kasutaja peab sisestama oma ees- ja perekonnanimi, email, telefoninumber ja kaks korda kinnitab oma parooli. Registreerimine võimalik ainult tavakasutajatele. Töötajate ja administraatori kontot on juba hardcode’itud projektis, mis on pigem halb, sest hiljem adminitraator ei saa ise parandada kasutajarolli või ise registreerida uue töötaja või admini kontot. Registreeerimis vorm on kasutajasõbalik, kus on nähtav, millised väljad on kohustuslikud.
Sisselogimiseks peab kasutaja klikkima Log In nuppule, mis asub menüüs. Logimisvormis korralikult töötab väljude valideerimine, mis tähendab, et kasutaja ei saa juhuslikult sisse logida ilma andmete sisestamist. Sisselogimiseks peab kasutaja sisestama oma emaili (kasutajanimi) ja parooli. Samuti tahaks märkida, et arendajad lisasin Remember me nuppu, mis on väga mugav lahendus, näiteks, adminite või töötajate jaoks, kellel ei ole aega logimisandmete sisestamiseks. Samal lehel asub ka link uue kasutaja registreerimislehele ja Log In nupp.
Süsteemis on kolm kasutajate gruppi: tavakasutaja, töötaja ja administraator. Tavakasutaja ehk kliendil on ligipääs remonditeenuse broneerimislehele ja oma broneeringute ülevaade lehele. See funktsionaalsus on klientrakenduses hästi täidetud. Kasutaja näeb ülevaates kõik tema broneeringute detailid ja võib kontrollida, kui tema broneering on aktsepteeritud. Broneeringuvorm on päris loogiline. Arendajad on toonud mugava lahendust kuupäeva ja aja valimiseks, kuid tahaks ikka näha selles süsteemis võimaluse valida sobiliku ajasloti, aga me usume, et seda võite juba järgmises versioonis parandada.
Services lehel võiks viidata andmete laadimisele, muidu läbi klikates jääb tunne, nagu veebiteenus ei tööta. Bootstrappi kasutad tundub nagu ei oldaks olnud arvestatud äärejuhtumitega eri akna laiust muutes. Tervitus ja log out lähevad eraldi navigatsiooniriba teisele reale. Navigatsiooniribal võiks olla kasutajakogemuse huvides ära näidatud, millisel lehel parasjagu ollakse. “Contact” leht näeb kuidagi tühi välja. Ning võiks navigatsiooniribal olla menüünuppudest pareimpoolseim. “Car” lehel võiks väljalaskeaasta olla lihtsalt täisarvuna sisestatav. Kindlat kuupäeva ei oska nagunii öelda.
Samal ajal oleks mõistlik näidata klienditele teavitusi, kui mingi väli ei ole täidetud või valitud. Näiteks, kui tavakasutaja teeb broneeringu ning unustab enne broneeringut oma lisada või nimekirjast autot valida. Sel juhul kasutaja ei näe teavitusi, et see väli pole täidetud ja andmed puuduvad. Selle probleemi lahenduseks me pakkume tiimile panna igale väljale nuppu, mis viib, näiteks, uue auto lisamislehele. See teeb protsessi natuke mugavamaks kasutajate jaoks.
Väga praktiline lahendus on broneeringute ajalugu lehel, kus kasutaja võib näha informatsiooni oma tuleviku visiiti kohta. Sellel lehel ka näidatakse kasutajale, kui kliendi päring oli aktsepteeritud või mitte. Selleks, et teha tabeli kasutamise mugavamaks, me pakkume broneeringustaatuse näidata sõnadega.
“Car” lehel võiks väljalaskeaasta olla lihtsalt täisarvuna sisestatav. Kindlat kuupäeva ei oska nagunii öelda. Kui juhuslikult ei ole seda auto tüüpi rippmenüüs olemas, siis võiks kasutaja saada ise kirjutada auto tüübi. Auto võiks saada lisada ka “booking” lehel, siis ei peaks nö “eeltööd” tegema. Pärast bookimist võib klientrakendus näiteks küsida, et kas klient tahab auto edaspidiseks kasutamiseks salvestada. “Nice to have” funktsionaalsus, kus ühele bookimisele saab mitu erinevat tööd registreerida on puudu. Ka teenusetüübi ja teenusega võiks saada klient ise märkida, mida tahab auto juures parandada. Üleüldiselt kahtlustan ma, kas autoparanduses oleks selline IT lahendus üldse paslik. Autoparandus ei ole nagu e-pood, kus klient teab, mida ta tahab.Samuti on autoparandustes erinevaid pakutavaid teenuseid on määratlematu arv. Seetõttu, kui teenuseid hakata andmebaasi kandma, võib kliendi täitsa ära ehmatada näidates lehekülgede kaupa erinevaid teenuseid, mille vahel tema peaks oskama valida. Sageli on autoparandustes erinevaid töid liiga palju, et neid kuskile andmebaasi üles märkida ning hind on sageli individuaalne olenevalt töö raskusest, auto mudelist, auto olukorrast jne. Seetõttu oleks võibolla parem lahendus, kui klient saaks ise kirjutada just oma auto sümptomite kohta, kui tegemist ei ole nö lihtsa õlivahetusega. Pärast seda võiks rakendus luua mehhaaniku ja kliendi vahel suhtluskanali (kasvõi meil),mille kaudu saaks mehhanik ise teha hinnapakkumise ning ajaennustuse, kaua auto parandus võiks aega võtta. Teenuse broneerimisel võiks klientrakendus ka kohe näidata, millised ajad on saadaval ning millised mitte. Selle jaoks tõsi peaks backendi päris palju ringi tegema. Book for garage nupp võiks asuda ekraani paremas servas ning olla prominentsemalt esitletud.
Töötaja õigusega kasutaja saab kasutada kõikide broneeringute ülevaade. Töötajal on võimalus näha kõik broneeringud, kuid kasutaja ei saa need aktsepteerida või kustatada, seda teeb ainult admin kasutaja. Väga mugav, et administraator või töötaja võivad filtreerida broneeringud. Kahjuks, mingil põhjusel nii töötaja, kui ka admin ei saa ise lisada uue broneeringu, mis on ebamugav, kui meil on klient, kes tegi broneeringut kohapeal või telefoni teel.
Koodi kohta ei oska palju öelda, kuna kõik tundub olevat tehtud üsnagi šablooni järgi. Mõned nipet näpet küll mainiks: indeks lehe div element ei ole suletud, layout.cshtml lehel oli üks üleliigne div silt, forgot password ja external login viewmodelid on samad (DRY), usercontrolleri summary on “Setting for getting user data”, millest ma täpselt aru ei saa.
Veel mõned pakkumised meie tiimi poolt:
- Admin kasutajal puudub võimalus määrata kasutaja adminiks või töötegijaks.
- Broneeringute arhiivist tahaks ka näha broneeringute lõplikud hinnad. Sellised andmed näeb ka tavakasutaja. Saab need andmed näidata vaid adminile ja tavakasujatele, kes tegi broneeringut.
- Tahaks ka näha “muutmise” funktsionaalsust kõikides kohtades, kus seda võib lisada ja kustutada. (kas see tähendab, et klientrakenduses CRUD pole täidetud?)
Klientrakendus on loodud Ajax sktiptide kasutades ning kood on jaotatud loogiliselt ja samas ka sarnaselt sellega, kuidas on reaalselt välja kuvatud. Klientrakendus on hästi ja loogiliselt struktureeritud. Kokkuvõtteks võib väita, et antud klientrakenduses on realiseeritud suurem osa vajalikke asju, kuid võib teha veel rohkem. Plussiks võib tuua rakenduse stabiilne töö ning täidetud must-have funktsionaalsus. Rakendus on ülesehituselt arusaadav ja mugav.
Veebiteenuse retsensioon meeskonnale Garagefy
Majandusliku poole pealt on küsitav, kas selline IT lahendus tasub ennast ära. Minu arvates ei ole autoparanduses võimalik piisavalt automatiseerida arvestades valdkonna töövoo ebastandartsust. Enamasti klient ei tea, mida ta tahab vaid küsib mehhanikult, sellest pikemalt aga hiljem. Hea, et on kasutatud swaggerit, mis teeb veebiteenuse retsenseerimise kohe palju lihtsamaks. Sellegipoolest oleks võinud paari sõnaga dokumentatsiooni täiendada näiteks kirjelduste näol seal, kus meetodid ei ole iseenesest mõistetavad. Kuigi klientrakenduses olid funktsionaalsused täidetud jääb koodi kvaliteet vajaka. Veebiteenuse kontrollerite meetodid teevad päringuid otse ApplicationDBContext-i. Vahekihid on nagu tehtud, aga kasutusele neid võetud pole. Puuduvad nii factory-d kui ka service-id. Samuti ei ole võetud kasutusele liideseid, mis ei võimalda omakorda sõltuvuste süstimist. Repositooriumites ei ole kohandatud funktsionaalsusi. Ausalt öeldes on raske 600 sõna kirjutada nii vähese abstraktsiooniga projekti kohta. Ideaalis teeksid kontrollerite meetodid päringuid vastavatele teenustele, mis omakorda teeksid päringuid Unit Of Work-ile, mis omakorda koordineeriks repositooriume, et siis need omakorda andmebaasiga suhtleks. Mina oleks pigem eelistanud katkist klientrakendust ning korralikku abtraktsiooni. Selle asemel aga on tiim keskendunud rohkem funktsionaalsuste saavutamisele. Arvestades aga aine suunitlusega peab küsima, kas funktsionaalsus on ikka kõige tähtsam. Domeenist parema ülevaate saamiseks oleks võinud teha ERD diagrammi. Bookingarchive olem tundub veidi ülenormaliseeriv. Sellest tuleneva funktsionaalsuse oleks olnud võimalik saavutada ka vaid mõne atribuudiga Booking olemil.
Probleem ja lahendus
Kuna hetkel on kontrolleritesse otse kirjutatud päringud andmebaasile, tuleneb sellest mitmed probleemid:
- Kui tahetakse minna üle uuele andmebaasile, siis peab kontrolleri uuesti kirjutama
- Veatestimisel on raske luua testandmebaase
- Kuna liideseid pole kasutatud, on kood raskesti loetav
- Kuna kood on raskesti loetav, eriti kui projekt peaks kasvama, siis on ka edasine arendus ja haldus raskendatud
Lahendus: Nagu ka aine raames on räägitud, tuleks alustada mõningasest abstrakteerimisest. Kui siduda äriloogika tihedalt tehnoloogiatega, mida kasutatakse äriloogika implementeerimisel, jääb rakendus üsnagi paindumatuks. Kui ühe raksuga on raske minna üle kõikidele arendusmustritele, siis hea nipp oleks liikuda andmebaasi poole pealt kontrollerite poole. Esiteks näiteks võtta kasutusele repositooriumid, saades konkreetsetest Persistance meediumist iseseisvuse. Pärast seda, kui on tahtmist transaktsioonihaldust implementeerida, peaks võtma kasutusele UOW. Selleks, et sügavamaid olemeid klientrakendusele saata ja vältida liikselt andmete saatmist tuleks pärast seda kasutusele võtta DTO-d ning samuti võiks äriloogika liigutada Service-itesse.