Hammas

From ICO wiki
Jump to navigationJump to search

Meeskond

  • Sten Lunden

Projekti kirjeldus

E-poe lahendus hambaraviseadmeid ja -tehnikat müüvale ettevõttele. Eesmärk on ehitada võimalikult õhuke e-kommertskiht mis vastaks minimaalsetele ärilistele nõudmistele. Samas peaks lahenduse arhitektuur olema võimalikult modulaarne, võimaldades lisaarendusi tulevikus.

Minimaalsed ärilised nõudmised

Poe avalik osa (storefront)

  • Ladustatud toodete kuvamine
    • Otsing
    • Tootegrupi, hankija filtreering
    • Toodete variatsioonid
  • Hinnakujundus
  • Ostukorvihaldus
  • Omniva ja SmartPosti pakiautomaatide valikud transpordimeetodites
  • Integratsioon Maksekeskuse API'ga
  • Kasutajate autoriseerimine, konto, ostuajalugu
  • Lokaliseeritud EE\RU\EN
  • SEO

Poe haldusosa (backend)

  • Hinnakirja, hankijate, tootegruppide haldus (CRUD)
  • Müügikampaaniate haldus
    • Toote, hankija, tootegrupi tasemel
    • Protsentuaalne ja fikseeritud allahindlus
    • Kampaania kestus
    • Kampaania sihtgrupp (sisseloginud või VIP kliendid jne)
  • Dashboard (nice to have)
    • Viimased tellimused
    • Registreerunud kasutajad
    • ...

Skoobist (momendil) väljas

  • Responsive design
  • Väljatrükid (arve, tellimus) ja aruandlus ==> SSRS(?)
  • Tootepildid

Tehniline baas

  • ASP.NET Core 2.0 (API)
  • AngularJS (klient)
  • IdentityServer 4
  • Azure hosting

Andmemudel

Põhilised kasutusjuhud

  • Lõppklient
    • Ligipääs ainult storefrondile
    • Soovi korral autendib ennast
    • Otsib tooteid
    • Lisab ja eemaldab tooteid oma ostukorvist
    • Viib ostuprotsessi lõpuni:
      • ..valides transpordimeetodi
      • ..valides maksemeetodi
    • Kui kasutaja on autenditud jääb ostusündmus kasutaja ajalukku
  • Administraator
    • Ligipääs poe halduslehele
    • Saab luua uusi tooteid ja toodetega seotud olemeid
    • Saab hõlpsasti navigeerida hinnakirjas ja seda muuta (in-line edit)
    • Saab defineerida müügikampaaniaid ja ad-hoc allahindlusi
    • Omab ülevaadet põhilistest ärilistest indikaatoritest
      • Tellimuste arv
      • Klientide arv
      • ...

XML

Andmefail

<?xml version="1.0" encoding="utf-8"?>

<Products>
  <Product id="727" parentId="600">
    <Name>
      <et-EE><![CDATA[TWINKY STAR refill, roheline 5cm 10g]]></et-EE>
      <en-GB><![CDATA[TWINKY STAR refill, green 5cm 10g]]></en-GB>
      <fi-FI><![CDATA[TWINKY STAR refill, vihreä 5cm 10g]]></fi-FI>
    </Name>
    <Prices Sales="860.00" Special="791.20" />
    <Properties>
      <Property>
        <Type><![CDATA[Värv]]></Type>
        <Value><![CDATA[Roheline]]></Value>
      </Property>
      <Property>
        <Type><![CDATA[Läbimõõt]]></Type>
        <Value><![CDATA[5cm]]></Value>
      </Property>
      <Property>
        <Type><![CDATA[Kaal]]></Type>
        <Value><![CDATA[10g]]></Value>
      </Property>
    </Properties>
    <Availability>
      <Stock id="1">
        <Name><![CDATA[Maneezi]]></Name>
        <AvailableQty>25.00</AvailableQty>
      </Stock>
      <Stock id="2">
        <Name><![CDATA[Hansakeskus]]></Name>
        <AvailableQty>19.00</AvailableQty>
      </Stock>
    </Availability>
  </Product>
  <Product id="231">
    <Name>
      <et-EE><![CDATA[ENDO BOX Dia-Dent 60+4pesa Medium ümar]]></et-EE>
    </Name>
    <Prices Sales="17.50" />
    <Properties />
    <Availability>
      <Stock id="2">
        <Name><![CDATA[Hansakeskus]]></Name>
        <AvailableQty>3.00</AvailableQty>
      </Stock>
    </Availability>
  </Product>
  <Product id="190">
    <Name>
      <et-EE><![CDATA[DENTIRO Light 5l]]></et-EE>
      <ru-RU><![CDATA[Light 5л]]></ru-RU>
    </Name>
    <Prices Sales="24.50" Special="14.95" />
    <Availability>
      <Stock id="1">
        <Name><![CDATA[Maneezi]]></Name>
        <AvailableQty>114.00</AvailableQty>
      </Stock>
      <Stock id="2">
        <Name><![CDATA[Hansakeskus]]></Name>
        <AvailableQty>223.00</AvailableQty>
      </Stock>
    </Availability>
    <Properties>
      <Property>
        <Type><![CDATA[Varjund]]></Type>
        <Value><![CDATA[A4]]></Value>
      </Property>
    </Properties>
  </Product>
</Products>

Skeemifail

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Products">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Product">
          <xs:complexType>
            <xs:sequence>
              <xs:choice maxOccurs="unbounded">
                <xs:element name="Name">
                  <xs:complexType>
                    <xs:sequence>
                      <xs:element name="et-EE" type="xs:string" />
                      <xs:element minOccurs="0" name="ru-RU" type="xs:string" />
                      <xs:element minOccurs="0" name="en-GB" type="xs:string" />
                      <xs:element minOccurs="0" name="fi-FI" type="xs:string" />
                    </xs:sequence>
                  </xs:complexType>
                </xs:element>
                <xs:element name="Prices">
                  <xs:complexType>
                    <xs:attribute name="Sales" type="xs:decimal" use="required" />
                    <xs:attribute name="Special" type="xs:decimal" use="optional" />
                  </xs:complexType>
                </xs:element>
                <xs:element name="Properties">
                  <xs:complexType>
                    <xs:sequence minOccurs="0">
                      <xs:element maxOccurs="unbounded" name="Property">
                        <xs:complexType>
                          <xs:sequence>
                            <xs:element name="Type" type="xs:string" />
                            <xs:element name="Value" type="xs:string" />
                          </xs:sequence>
                        </xs:complexType>
                      </xs:element>
                    </xs:sequence>
                  </xs:complexType>
                </xs:element>
                <xs:element name="Availability">
                  <xs:complexType>
                    <xs:sequence>
                      <xs:element maxOccurs="unbounded" name="Stock">
                        <xs:complexType>
                          <xs:sequence>
                            <xs:element name="Name" type="xs:string" />
                            <xs:element name="AvailableQty" type="xs:decimal" />
                          </xs:sequence>
                          <xs:attribute name="id" type="xs:int" use="required" />
                        </xs:complexType>
                      </xs:element>
                    </xs:sequence>
                  </xs:complexType>
                </xs:element>
              </xs:choice>
            </xs:sequence>
            <xs:attribute name="id" type="xs:int" use="required" />
            <xs:attribute name="parentId" type="xs:int" use="optional" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Transformatsioon HTMLi

Transformatsioon nähtav siin

<?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="/Products">
      <html>
        <head>
          <h3>Tooted</h3>
        </head>
        <body style="list-style-type:square">
          <xsl:for-each select="Product">
            <div style="border-style:solid;margin-bottom:5px;width:33%">
              <xsl:value-of select="Name/et-EE"/> 
            <xsl:if test="@parentId">
              <span style="color:red"> Toode on hierarhias!</span> 
            </xsl:if>
              <br/><br/>
              <xsl:variable name="n" select ="count(Name/*)"/>
              <xsl:if test="$n > 1">
                Tõlked:
                <!-- Tõlked. Kõik mis pole et-EE -->
                <xsl:for-each select="Name/*[not(local-name()='et-EE')]">
                  <li>
                    <xsl:value-of select="local-name()"/>
                    <xsl:text>: </xsl:text>
                    <xsl:value-of select="."/>
                  </li>
                </xsl:for-each>
                <br/>
              </xsl:if>
              Hind <xsl:choose>
                <xsl:when test="Prices/@Special">
                <strike>
                  <xsl:value-of select="Prices/@Sales"/>                  
                </strike>
                <xsl:text> </xsl:text>
                <xsl:value-of select="Prices/@Special"/>
                  <br/>
                  <br/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:value-of select="Prices/@Sales"/>
                  <br/>
                  <br/>
                </xsl:otherwise>
                
              </xsl:choose>
              <xsl:if test="Properties/*">
                Atribuudid:
                <xsl:for-each select="Properties/Property">
                  <li>
                    <xsl:value-of select="Type"/>
                    <xsl:text>: </xsl:text>
                    <xsl:value-of select="Value"/>
                  </li>
                </xsl:for-each>
                <br/>
              </xsl:if>
              <xsl:if test="Availability/*">
                Saadavus:
                <xsl:for-each select="Availability/Stock">
                  <li>
                    <xsl:value-of select="Name"/>
                    <xsl:text>: </xsl:text>
                    <xsl:value-of select="AvailableQty"/>
                    <xsl:text> tk.</xsl:text>
                  </li>
                </xsl:for-each>     
                <br/>
              </xsl:if>
            </div>
          </xsl:for-each>
        </body>
      </html>
    </xsl:template>
</xsl:stylesheet>

Transformatsioon XMLi

Grupeerime tooted laopõhiselt. Transformatsioon nähtav siin.

<?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" media-type="text/xml" />


  <xsl:key name="groups" match="Product/Availability/Stock/Name" use="."/>

  <xsl:template match="Products">
    <!-- get unique stocks by Muenchian grouping http://www.jenitennison.com/xslt/grouping/muenchian.html -->
     <xsl:for-each select="Product/Availability/Stock/Name[generate-id()=generate-id(key('groups',.)[1])]">
       <xsl:variable name="stockID" select="../@id" />
       <stock>
         <xsl:attribute name="id">
           <xsl:value-of select="$stockID"/>
         </xsl:attribute>
         <name>
           <xsl:value-of select="."/>
         </name>

         <xsl:for-each select="key('groups',.)">

           <product>
             <xsl:attribute name="id">
               <xsl:value-of select="../../../@id"/>
             </xsl:attribute>
             <xsl:attribute name="qty">
               <xsl:value-of select="../../../Availability/Stock[@id=$stockID]/AvailableQty"/>
             </xsl:attribute>
             <name>
               <xsl:value-of select="../../../Name/et-EE" />
             </name>
           </product>
         </xsl:for-each>
         </stock>
       </xsl:for-each>

   
  </xsl:template>
</xsl:stylesheet>

Retsensioonid

Veebiteenuse retsensioon meeskonnale GoFood

Projekti arhitektuur vastab headele tavadele ja on üles ehitatud õppejõudude soovituste kohaselt - kasutusel teenuste kiht, mis omakord kasutab uow'd ja repositooriumeid. Kasutusel nii generic kui ka custom repositooriumid. Kood on kirjutatud vastu liideseid (Interface). Kasutusel ka DTO'd mis pannakse kokku Factorites.

Domeenimudel on samuti viisakas. Eksisteerib 12 olemit + kasutajate tabel. Stringid on limiteeritud, PK'd ja FK'd olemas.

Kiiduväärt on ka see, et erinevalt mitmetest teistest proovitud projektidest buildis see esimese katsega ja Migrationid läksid kohe läbi. Eks oleks nice-to-have kui tekitatakse ka kohe mõned andmed millega testida (eriti kuna clientapp kippus vigu andma kui mõned objektid olid tühjad), aga ei olnud suur probleem ka ise andmeid tekitada.

Iga domeeniobjekti kohta eksisteerib kontroller, kuhu on süstitud samanimeline teenus. Siin on ehk tegu väikest viisi liiasusega, sest API seisukohast võiks siduvate tabelite andmeid välja anda läbi root-objekti. Näiteks ProductInShoppingListControlleri võiks ära kaotada ning ShoppingListController võiks neid andmeid välja anda olemasolevate päringute lisana (ShoppingListDTO võiks omada kollektsiooni List<ProductInShoppingList>, mille saaks täita kui EFShoppingListRepositoryst välja anda andmed kujul _db.ShoppingList.Include(x => x.ProductsInShoppingLists). Alternatiivina võiks ProductInShoppingListControlleri meetodid üle viia ShoppingListControllerisse. Sõltumata sellest mis meetodit kasutada, jätaks refereerija alles kontrolleri ainult iga põhilise ’päriselu’ objekti kohta – Store, ShoppingList, Product, NutritionPlan etc ja serveeriks andmeid ainult läbi nende. See teeks ka API tarbija elu lihtsamaks, kuna objektid on intuitiivsemad.

Kõik kontrollerid (v.a SecurityController) on Authorize annotationiga illustreeritud nagu peabki. Seadistatud on ka Swaggeri endpoint.

API funktsionaalsuse poolest on realiseeritud kasutajate loomine, autentimine ja andmebaasi salvestamine ning kontrollerid on valmis iga domeeniobjekti CRUD tegevusteks. Teenuste kihis keerulisemat äriloogikat veel ei ole, toimub suht üks-ühele DTO vs domeeniobjekti mappimine ning seejärel repostiooriumisse või tagasi kontrollerisse saatmine.

Lõpetuseks vaataks üle kuidas projekti suhestub hindamiskriteeriumitega:

  •  Majanduslik mõtlemine (kas loodav teenus ja rakendused oleks kasutatav ka ärilistel eesmärkidel) – Toote sihtgrupp on paigas, idee on üsna huvitav ja ambitsioonikas. Projekti sajaprotsendilise teostuse korral oleks monetiseerimine kindlasti mõeldav
  •  Mobiilirakendused/Angularis/reactis loodud klientrakendus – klientrakendus teostatud WPF’is. Konkreetne rakendus vajaks ilmselt Native\Hybrid mobiilset äppi, kuna kasutaja peaks tegevusi tegema mobiilsest seadmest erinevatel aegadel päeva jooksul
  •  Kogukondade kaasamine – sihtgrupp on oma tervisest lugu pidavad inimesed. Ärilise edu korral võib see aidata inimestel teha igapäevaselt paremaid\teadlikumaid valikuid toidu osas ja omab seega positiivset mõju ühiskonnale
  •  Kasutajamugavus – sõltub eelkõige klientäpi realisatsioonist. API kasutusmugavuse kohta on kommentaarid antud eelnevalt. Swaggeri olemasolu on väga abiks.
  •  Läbimõeldud töökorraldus – projektil on versioonihaldus (millesse refereerija millegipärast sisse ei saanud), wiki lehel kirjeldatud lühidalt üldine idee ja soovitud funktsionaalsus. Eks analüüs võiks detailsem olla (andmeskeem jne), aga üldjoontes on lähenemine struktuurne
  •  Lisavõimaluste realiseerimine (vt näidisteemad) – klientäpp on veel üsna algusfaasis, seega on hetkel keskendutud pigem baasfunktsionaalsuse arendamisele
  •  Korraliku arhitektuuriga kirjutatud kood – projekti ülesehitus korralik, võiks veel julgemalt eksperimenteerida linq’i ja keerulisemate äriloogikate ehitamisel