• No results found

Beslutsstöd för prissättning till webbutik

N/A
N/A
Protected

Academic year: 2022

Share "Beslutsstöd för prissättning till webbutik"

Copied!
23
0
0

Loading.... (view fulltext now)

Full text

(1)

Beslutsstöd för prissättning till webbutik

Projektrapport

22 september 2011

This paper is about the development of an application that collects and processes market pricing data. This is used by an online store to gain an overview of the market they operate in. The paper begins with a brief outline of the plan for development and continues with a detailed description of the final product.

The application was developed in Java and heavily utilize threading, es- pecially for downloading data from the Internet. It is built on a client- server architecture that communicates with a text-based protocol. Overall the project is considered a success, even though some things, such as the memory footprint, could have been improved.

Examensarbetare:

David Ersson - dersson@kth.se Examinator:

Fredrik Lundevall - flu@kth.se

Skolan för informations- och kommunikationsteknik Kungliga tekniska högskolan

Kista

(2)

Innehåll Innehåll

Denna rapport handlar om hur en applikation utvecklades för att sam- la prisinformation om en given marknad och sedan tillgängliggöra den på ett strukturerat sätt. Målet med detta är att en webbutik ska få en över- sikt över hur de förhåller sig till sina konkurrenter. Rapporten börjar med en kort beskrivning av hur planen för utvecklingen såg ut innan utveckling påbörjades. Den fortsätter sedan med en ganska detaljerad beskrivning av hur slutprodukten blev.

Applikationen utvecklades i Java och drar till stor del nytta av trådning för en öka prestandan. Det gäller speciellt när det handlar om uppgifter som involverar kommunikation med källor på internet. Applikationen bygger på en klient-server-struktur som kommunicerar via ett textbaserat protokoll.

Slutprodukten ses som lyckad även om vissa saker, så som dess minnesför- brukning, skulle kunna förbättras.

Innehåll

I. Förstudie 4

1. Inledning 4

2. Programmodell 5

2.1. Datalagret . . . . 5

2.2. Logiklagret . . . . 6

2.3. Presentationslagret . . . . 6

3. Tekniska problem 6 3.1. Programmeringsspråk . . . . 6

3.2. Inhämtande av data . . . . 7

3.2.1. Datahämtning via webbtjänst . . . . 7

3.2.2. Datahämtning via web scraping . . . . 7

4. Liknande projekt 8 4.1. 80legs . . . . 8

4.2. Mozenda . . . . 9

II. Genomförande 10 5. Containrar 10 5.1. ReferenceProduct . . . . 10

5.2. CompetitorProduct . . . . 11

5.3. ProductPrice . . . . 11

(3)

Innehåll Innehåll

6. Datalagret 11

6.1. AdapterHandler . . . . 12

6.2. Adapter . . . . 12

6.3. ReferenceAdapter . . . . 12

6.4. CompetitorAdapter . . . . 13

7. Logiklagret 14 7.1. DataHandler . . . . 14

7.2. Server . . . . 15

7.3. ClientHandler . . . . 15

7.3.1. Protokoll . . . . 15

7.3.2. Funktioner . . . . 15

8. Presentationslagret 17 8.1. ClientCommander . . . . 17

8.2. NetworkThread . . . . 18

8.3. GUI . . . . 18

8.3.1. Layout . . . . 19

III. Diskussion och slutord 20 9. Förbättringspotential 20 9.1. Arbetsminne . . . . 20

9.2. Styrklasserna . . . . 20

9.3. Prestanda . . . . 20

9.4. Konfigurationsfil . . . . 21

10. Slutord 22

(4)

1 INLEDNING

Del I.

Förstudie

1. Inledning

I en rapport från Posten som kom i april 2010

1

kan man läsa att omsättningen för dis- tanshandel fördubblats de senaste fem åren. I rapportens förord säger Per Mossberg, kommunikationsredaktör på Posten Norden AB, att “För nutidens konsumenter är dis- tanshandel främst synonymt med att hitta och köpa det man söker på nätet snabbt, enkelt och prisvärt“. Det är därför viktigt att man som nätbutik på dagens marknad har konkurrenskraftiga priser. Ingen kommer köpa något från en dyr leverantör om samma produkt levereras av en billigare leverantör med jämförbar standard.

Detta projekt handlar om att jämföra en given onlinebutiks priser med alla konkur- rerande butikers priser. Det kommer i denna rapport antas att alla konkurrenter också är onlinebutiker, men det finns inget som säger att det måste vara så. Dock är en förut- sättning att deras priser finns online.

Beställaren av detta projekt har gett mig i uppdrag att eliminera deras behov av att manuellt söka igenom konkurrenters hemsidor efter deras priser. De vill istället ha en lösning där de får sådan informationen serverad på ett strukturerat sätt automatiskt.

Med detta hoppas de förutom att minska arbetsbördan också minska risken för att mindre vanliga produkter glöms bort. Ett automatiskt system skulle kunna behandla alla produkter i deras utbud, medan en människa måste begränsa sig till de vanligaste produkterna för att inte få en omöjlig uppgift.

Den givna butiken kommer refereras till som ”beställarens” butik och de konkur- rerande butikerna som ”konkurrenter”.

1http://www.posten.se/img/cmt/PDF/distanshandeln_idag_2010.pdf - 2011-04-05 - Distanshandeln idag 2010

(5)

2 PROGRAMMODELL

2. Programmodell

Figur 1: Logisk modell - Pricer är arbetsnamnet på projektet

För att lösa beställarens problem är planen att skapa en applikation enligt modellen i figur 1 (en mer komplett modell av den färdiga produkten finns som appendix till denna rapport). Tanken är att för varje produkt som beställaren har i sitt sortiment kontrolleras priset hos andra som också säljer den. Denna data kan beställaren sedan hämta ut på en strukturerad form och se hur de ligger i förhållande till marknaden för var och en av alla de produkter de säljer.

Modellen är inspirerad av Model View Controller (MVC)

2

och Three-tier

3

. Datalagret och logiklagret är logiska lager i samma applikation, som i sin tur agerar server mot presentationslagret.

2.1. Datalagret

Datalagret har som uppgift att samla in data från olika källor. Det finns två olika sorters källor; datakällor och referenskällor.

Datalagret har till uppgift att hämta prisinformation om alla produkter som referen- skällan tillhandahåller (alltså de produkter som beställaren säljer) från datakällorna. För att ha så dynamiskt stöd för datakällor som möjligt har varje datakälla en “adapter”

som beskriver hur just den datakällan ska läsas in. Det gör också att det är relativt

2http://www.oracle.com/technetwork/articles/javase/index-142890.html - 2011-04-01 - Java SE Appli- cation Design With MVC

3http://msdn.microsoft.com/en-us/library/ff647546.aspx - 2011-04-05 - Three-Tiered Distribution

(6)

2.2 Logiklagret 3 TEKNISKA PROBLEM

enkelt att lägga till nya datakällor eller uppdatera befintliga om behov skulle uppstå.

Efter inläsning skickas datan (formaterad) till logiklagret för vidare behandling.

2.2. Logiklagret

Logiklagret håller en lista med referensprodukter, matade från referenskällan i datala- gret. Den listan innehåller även information om konkurrenternas priser, matade från datakällorna i datalagret. På så vis kan man enkelt se hur referenspriset ligger i förhål- lande till konkurrenterna. För att enkelt kunna se vilka konkurrenter som är billigast pekas de ut av specifika referenser i respektive produktobjekt.

För att kunna automatisera procedurer baserat på inläst data finns det även krokar där man kan ansluta objekt för detta. När ett givet läge uppstår kallas respektive objekts aktiveringsfunktion. För exempel se algoritm 1.

Algoritm 1 Exempel på automatisk funktion (pseudokod)

i f ( r e f e r e n s p r i s > b i l l i g a s t e _ k o n k u r r e n t s _ p r i s ) {

i f ( b i l l i g a s t e _ k o n k u r r e n t s _ p r i s > i n k o p s p r i s ∗ m i n s t a _ v i n s t m a r g i n a l ) { s e t E g e t P r i s ( b i l l i g a s t e _ k o n k u r r e n t s _ p r i s ) ;

} }

2.3. Presentationslagret

Presentationslagret agerar gränssnitt åt användaren. Det är ett fristående program som ansluter mot logiklagret via nätverk för att hämta den data som användaren valt att få presenteras för sig. Denna data presenteras sedan på ett sätt som gör det enkelt för användaren att förstå den.

3. Tekniska problem

Det finns ett antal problem relaterat med detta projekt. Detta avsnitt kommer att be- handla de större av dem och hur jag har tänkt lösa dem.

3.1. Programmeringsspråk

Jag har valt att genomföra detta projekt i Java då det är ett välkänt språk som många hanterar, vilket underlättar underhåll och eventuell vidareutveckling. Det är också plat- formsoberoende så minimalt med jobb kommer behöva läggas på att porta produkten om beställaren skulle välja att byta plattform. Java har kritiserats för att generera rela- tivt långsamma program

4

. Nyare tester visar dock att Java är ungefär lika bra som sina

4http://www.spinellis.gr/blog/20050210/ - 2011-04-04 - The Efficiency of Java and C++

(7)

3.2 Inhämtande av data 3 TEKNISKA PROBLEM

konkurrenter (C++, C#)

5

och även om C slår dem alla med god marginal i samtliga av testerna så måste man komma ihåg att det till exempel i allmänhet tar mycket längre tid att utveckla i C.

3.2. Inhämtande av data

En av grundförutsättningarna för detta projekt är att det finns någon data att jämföra med. Den måste på något sätt både förvärvas och hållas uppdaterad. Föråldrad data är ointressant då prisbilden kan förändras snabbt

6

. För att åstadkomma detta finns två huvudsakliga metoder;

3.2.1. Datahämtning via webbtjänst

Webbtjänster används för att förenkla interaktion och kommunikation mellan tjänsten och mjukvara. En vanlig hemsida till exempel är (eller borde i alla fall vara) skapad för att presentera data för en människa som besöker den, men när man hämtar data via mjukvara för processering är man inte intresserad av saker som färg eller formatering.

Man är istället bara ute efter den faktiska datan så för att slippa skicka onödig informa- tion kan en webbtjänst

7

presentera ett API

8

med funktioner för att endast skicka datan, oftast på en känd form såsom XML

9

eller JSON

10

. Webbtjänster anses vara en del av webb 2.0

11

.

Fördelarna med att hämta data från en webbtjänst gentemot den äldre tekniken “web scraping” är överväldigande. Om det finns en webbtjänst som tillhandahåller datan man är ute efter så är det den man bör använda.

3.2.2. Datahämtning via web scraping

“Web scraping” (även känt under liknande namn så som “web farming”, “screen scrap- ing”, “web harvesting” eller på kort form endast “scraping”), är ett generellt namn för att samla in data genom att läsa av till exempel hemsidor maskinellt och sedan filtrera ut den data man är intresserad av

12

. Det är inte en optimal lösning, men om det inte tillhandahålls något API så kan det vara den enda lösningen. Eftersom scraping baseras

5http://www.cherrystonesoftware.com/doc/AlgorithmicPerformance.pdf - 2011-04-04 - Algorithmic Performance Comparison Between C, C++, Java and C# Programming Languages

6http://www.prisjakt.nu/bok.php?pu=1077180 - 2011-04-04 - Prisutveckling för Host

7http://www.w3.org/TR/ws-arch/#whatis - 2011-04-04 - Web Services Architecture

8Application Programming Interface - kan översättas till “mjukvarugränssnitt” och är en uppsättning funktioner som är tillgängliga att använda utanför mjukvaran som tillhandahåller dem

9http://www.w3.org/XML/ - 2011-04-07 - Extensible Markup Language

10http://www.json.org/ - 2011-04-07 - Introducing JSON

11http://oreilly.com/web2/archive/what-is-web-20.html - 2011-04-04 - What Is Web 2.0

12http://www.csd.uoc.gr/~hy565/newpage/docs/pdfs/papers/process_aggregation_web-services.pdf - 2011-04-05 - Process Aggregation Using Web Services - Citat: “Screen scraping and “web farm- ing” [5] techniques were developed where the aggregator accessed the source site as if it were a user and parsed the resulting Hyper Text Markup Language (HTML) to extract the information being aggregated.”

(8)

4 LIKNANDE PROJEKT

på filtrering av data tänkt för människor så är risken stor att applikationen som använ- der det slutar fungera om sidan ändras, även om datan finns kvar. För en människa är skillnaden liten om till exempel layouten ändras, men det kan vara förödande för ett scraping-filter. Den enklaste formen av scraping är att manuellt söka reda på informa- tionen man är intresserad av och sedan kopiera och klistra in den där man vill ha den.

Den metoden är uppenbarligen inte särskilt effektivt.

13

Det finns också en moralisk aspekt av scraping. Eftersom man hämtar data automa- tiserat så riskerar man dels att belasta hemsidan relativt tungt under tiden man gör det och dels ser man aldrig reklamen eller klickar på länkarna så inkomster baserade på länk-klick uteblir för hemsidans ägare

14

. Det är dock inte olagligt som så (om man inte ingått i ett avtal som säger annat för att få tillgång till informationen) utan det beror mer på vad man gör med informationen sedan. Det kan även argumenteras för att reklamaspekten är av mindre betydelse då många idag använder reklamblockering i sina webbläsare

15

.

Detta projekt kommer använda sig av en större svensk webbsida för prisjämförelser som datakälla till att börja med. Även om det kommer utebli reklamintäkter för dem så vill jag hävda att effekten av detta projekt är densamma som om någon hade suttit manuellt och slagit upp alla priser, då denna person ändå inte skulle vara intresserad av reklamen. Skillnaden är att nu går det att styra när belastningen ska ske.

4. Liknande projekt

Jag har inte lyckats hitta något bra akademiskt gångbart material om scraping. Detta tror jag beror på att det är en väldigt bred och ospecifik term som dessutom är relativt simpel att förstå, men specifik att implementera

16

. På internet kan man dock hitta projekt som använder sig av web scraping för att få fram information. Nedan följer en kort analys av två av dem.

4.1. 80legs

Tjänsten 80legs

17

tillhandahåller en onlinetjänst för att enkelt skapa egna så kallade 80apps, vilket motsvarar det som i modellen ovan kallas adaptrar. Dessa kör man sedan via deras servrar och får resultatet tillbaka. De har även vanligt förekommande ak- tiviteter som färdiga paket. Bland de färdiga paket som finns att tillgå finns bland annat reguljära uttryck (eng. regular expressions)

18

, ett system för att matcha text som är smidigt när man vill hämta ut given information ur en massa med text.

13I resten av detta dokument kommer scraping referera till den automatiserade formen, dels då det är den sortens scraping som menas om inget annat anges, och dels för att det underlättar semantiken.

14http://www.sentor.se/anti-scraping.html - 2011-04-05 - Anti-scraping

15https://addons.mozilla.org/en-US/statistics/addon/1865 - 2011-04-05 - Adblock Plus Statistics

16Detta är examensarbetarens kvalificerade gissning

17http://80legs.com/ - 2011-04-26 - Custom Web Crawlers, Powerful Web Crawling, and Data Extraction

18http://www.regular-expressions.info/ - 2011-04-26 - Regular-Expressions.info - Regex Tutorial, Exam- ples and Reference

(9)

4.2 Mozenda 4 LIKNANDE PROJEKT

Med hjälp av 80legs skulle man kunna skriva en väldigt effektiv adapter, men det går inte att ersätta hela datalagret då datalagret levererar datan till logiklagret på en strukturerad form. Det betyder att man hur som helst skulle behöva ett lager för att stukturera datan.

4.2. Mozenda

Mozenda liknar på många sätt 80legs. Man skapar sina egna agenter som man via ett intuitivt webbgränssnitt instruerar hur de ska läsa av sina mål. Den avlästa datan kan sedan till exempel laddas upp på en server eller skickas till valfri e-postadress. Även här är agenter samma sak som adaptrar i modellen ovan. Många stora företag använder sig av denna tjänst enligt deras egen hemsida

19

, däribland Microsoft, IBM och hotels.com.

Efter endast en snabb överblick verkar det som att det skulle vara möjligt att ersätta i stort hela datalagret med denna tjänst. Dock finns problemet att denna tjänst kostar en hel del att använda, särskilt om man har för avsikt att ladda väldigt många sidor

20

. Utan att ha testat Mozenda så ser den dock mycket bra ut på papper. Den skulle förmodligen kunna användas som en adapter vid behov.

19http://www.mozenda.com/ - 2011-04-26 - Data Extraction, Web Screen Scraping Tool, Mozenda Scraper

20http://www.mozenda.com/pricing - 2011-04-26 - Pricing plan

(10)

5 CONTAINRAR

Del II.

Genomförande

Innan genomgången av programmets uppbyggnad så är det på sin plats med en del förtydlingar. De produkter och källor man jämför med är “referenser”, men kan också refereras till som “beställarens”. Detta kan i förväg låta fullständigt uppenbart men kan ändå vara bra att hålla i minnet. Det finns också en del fall av “svengelska” då koden är skriven på engelska, men rapporten på svenska. För en komplett modell se slutet på denna rapport.

Först kommer de klasser som används som containrar (databehållare) att beskrivas.

De är skapade för att på ett logiskt sätt innehålla all nödvändig information om det de representerar. Efter det kommer lagren i figur 1 beskrivas i ordning, uppifrån och ner.

5. Containrar

För att hålla ordning och struktur på informationen som samlas in har det skapats ett antal objekt för att lagra den. Dessa containerobjekt hör inte till något specifikt lager i figur 1 utan används för att förmedla och lagra information mellan dem. De skapas i datalagret (avsnitt 6) och lagras sedan i logiklagret (avsnitt 7).

5.1. ReferenceProduct

Detta är en representation av en produkt som beställaren har i sitt sortiment. I den finns information om produktens namn, produktnummer och beställarens utpris på den.

Det finns också en lista med CompetitorProduct:s (se 5.2) och metoder för att hämta information om produkten.

ReferenceProduct är upplagd på så sätt att man är tillåten att ha flera referenser på samma produkt (antag att beställaren har flera olika butiker som säljer samma produkt men med olika priser). Man måste därför ange från vilken källa man lägger till ett referenspris. Referenser kan inte läggas in som konkurrenter. Flera butiker som alla är referenser kommer alltså inte att konkurrera med varandra. Försöker man göra det kommer den metoden returnera direkt, utan att faktiskt ha gjort något.

Exempel: Antag att en viss produkt säljs av de egna butikerna A och B för 10 och 5, och av konkurrent C för 8. Man skulle då för A få veta att C är billigaste konkurrent med 2 billigare, och för B skulle man få att C är billigaste konkurrent med -3 billigare.

En utskriven referensprodukt har formatet “produktens namn;produktnummer;beställarens pris på produkten;billigaste konkurrents namn;billigaste konkurrents pris

på produkten;hur mycket billigare billigaste konkurrent är”

(11)

5.2 CompetitorProduct 6 DATALAGRET

Figur 2: Flödesschema för Pricer.

5.2. CompetitorProduct

CompetitorProduct håller en representation av en konkurrents pris för en given produkt.

Syftet med dessa är att lägga till dem till en referensprodukt (5.1). Varje instans av en CompetitorProduct innehåller namnet på konkurrenten den representerar, dess pris på produkten och när informationen senast är uppdaterad. Den innehåller dock inte själva någon information om vilken produkt den handlar om, så den måste kopplas till en referensprodukt för att ha någon betydelse.

5.3. ProductPrice

ProductPrice används av ReferenceProduct(5.1) för att hålla isär olika referenser. Den in- nehåller endast namn på referensen och dess pris. Till skillnad från CompetitorProduct(5.2) så saknar denna tid för senaste uppdatering då detta förväntas vara senaste uppdaterin- gen.

6. Datalagret

Datalagret i figur 1 utgörs av en adapterhanterare och adaptrar. En adapter är en klass

som kommunicerar med omvärlden för insamlande av information. De kallas adaptrar

då de omvandlar extern information till Pricers gemensamma interna format (se avsnitt

5).

(12)

6.1 AdapterHandler 6 DATALAGRET

Adaptrarna är uppdelade i två grupper, referensadaptrar (ReferenceAdapter) och konkurrentadaptrar (CompetitorAdapter). Referensadaptrarna matar referensprodukter (5.1) till logiklagret (avsnitt 7) som i sin tur begär en uppdatering av var och en av de inkommande produkterna. Uppdateringar behandlas av konkurrentadaptrarna som frå- gar sin källa om vad andra leverantörer (konkurrenter) har för priser på given produkt och rapporterar tillbaka till datalagret i form av konkurrentprodukter (5.2). Figur 2 visar hur flödet går från uppstart till dess att försök att hämta data om alla produkter genomförts.

6.1. AdapterHandler

AdapterHandler håller reda på alla adaptrar som kopplas till systemet. Dess huvudsak- liga funktion är att sprida förfrågningar från DataHandler (7.1) till alla adaptrar som ska ha dem. AdapterHandler erbjuder metoder för att lägga till fler adaptrar (både ref- erensadaptrar och konkurrentadaptrar), hämta information om en produkt (baserat på produktnummer) från alla konkurrentadaptrar, visa vilka referenskällor som är kopplade till systemet och funktioner för att starta en ny uppdatering. Till skillnad från modellen i avsnitt 2 är det i slutprodukten tillåtet att ha ett obegränsat antal referensadaptrar då behov av fler än en fanns hos beställaren.

Spridningen av metodanrop görs i samtliga fall med (uppenbarligen något anpassat för situationen):

f o r ( CompetitorAdapter<T> a : t h i s . c o m p e t i t o r A d a p t e r s ) { a . r e q u e s t P r o d u c t I n f o ( pro du ctI D ) ;

}

Looptypen kallas “for-each” och är ett smidigt sätta att iterera över en datasamling utan att behöva bry sig om dess storlek. Den kan läsas ut som “för varje CompetitorAdapter i listan competitorAdapters, kalla på requestProductInfo med inparameter productID”.

6.2. Adapter

Adapter är en liten abstrakt klass som både ReferenceAdapter och CompetitorAdapter ärver från. Den försäkrar att alla adaptrar tillhandahåller metoderna “status()” och

“stop()”. Adapter implementerar Runnable vilket gör att den kan köras som en egen tråd. Det får också till följd att de klasser som ärver från Adapter kan köras som egna trådar. Mer om det i respektive adaptertyp.

6.3. ReferenceAdapter

En refernesadapter är en adaptrar som implementerar ReferenceAdapter. Den läser in beställarens produkter i systemet. ReferenceAdapter är en abstrakt klass som bara till- handahåller basfunktionaliteten och lämnar det sen till implementation att stå för de- taljerna om hur inläsningen ska genomföras.

Varje referensadapter (alltså implementation av ReferenceAdapter) måste kalla på

ReferenceAdapter:s konstruktor med ett namn på adaptern och ett mål. Målet är var

(13)

6.4 CompetitorAdapter 6 DATALAGRET

data ska hämtas från och representeras med en String. Anledningen till att det är en String och inte något annat är att man inte ska vara begränsad till att målet ska vara till exempel en URL eller en fil. Namnet bör vara samma namn som eventuellt kan rapporteras som konkurrent av en CompetitorAdapter (6.4). Referensprodukten kon- trollerar nämligen om konkurrenten som läggs till egentligen är en referens, och i så fall läggs den inte in som konkurrent.

En referensadapter är uppdelad i tre trådar. En tråd som producerar data, en som behandlar datan och en som (eventuellt) övervakar de två första trådarnas förehavanden.

Den första tråden producerar data genom att läsa av sin källa (till exempel genom att ladda ner en fil från en URL) och placera inläst rådata i en kö, element för element.

Datan som läses in via den externa källan refereras till som rådata då den kan komma i vilken form som helst (till exempel som en textsträng). Då nerladdning är en operation som kräver interaktion med en extern part sköts det i en egen tråd så störningsmomentet för den externa parten blir så minimalt som möjligt.

Tråd nummer två “konsumerar” rådata från tidigare nämnd kö och omvandlar den till referensprodukter (5.1). Den skapade referensprodukten läggs till DataHandler:s (7.1) samling referensprodukter via DataHandler:s metod addReferenceProduct(...).

När man startar en referensadapter startas både den producerande tråden och den konsumerande tråden via metodkall till respektive getProducer och getConsumer. De båda metoderna är abstrakta i ReferenceProduct så en implementation måste tillhan- dahålla dessa och de ska returnera varsin Runnable.

Den tredje tråden är en statustråd. Eftersom ReferenceProduct ärver från Adapter (6.2) körs även själva ReferenceAdapter i en egen tråd. Det finns dock inga krav på att denna tråd ska göra något. Efter att producent- och kunsumenttråden startats kallas den abstrakta metoden monitor() och det är där eventuell övervakningsfunktionalitet implementeras. Metoden kallas dock bara en gång, så om man vill ha något som kör kontinuerligt måste man lägga det som en loop i implementation av monitor(). Vill man inte implementera någon särskild övervakning implementerar man bara en tom monitor().

6.4. CompetitorAdapter

CompetitorAdapter är en abstrakt klass för de adaptrar som ska läsa av konkurrenters produktinformation. Den har funktionalitet för att tråda upp hämtningen av konkur- rentinformation och interaktion med logiklagret färdigimplementerat. Det som måste implementeras när man skapar en konkurrentadapter (alltså en adapter som ärver från denna klass) är den funktion som faktiskt hämtar datan, getProductInfo(T productInfo).

Den tar in ett produktnummer (till exempel ISBN för böcker) och ska returnera en lista med CompetitorProduct:s.

Trådningen i CompetitorAdapter sköts av en ExecutorService

21

av typen Execu- tors.newCachedThreadPool()

22

. En cached thread pool har egenskapen att när man ger

21http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ExecutorService.html - 2011-05-30 - Interface ExecutorService

22http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Executors.html - 2011-05-30

(14)

7 LOGIKLAGRET

den uppdrag kommer den starta nya trådar efter behov. Detta gör att den har lite längre uppstartstid än till exempel en fixed thread pool (som även den finns i Executors), som har egenskapen att den alltid har ett fixt antal trådar som ligger och väntar på jobb. Det man dock vinner med cached thread pool är att när det inte finns något jobb att göra ligger det inte trådar och väntar, vilket kan ses som en fördel i denna applikation då den bara ska köra uppdateringar en mindre del av sin körtid. Nya trådar startas allt eftersom de redan körande trådarna inte hinner med att tömma arbetskön. Detta kontrolleras av CompetitorAdapter:s egen tråd.

För att skydda mot att det skapas massvis med trådar som öppnar varsin nätanslut- ning finns det en semafor. En semafor är ungefär som en korg med bollar i, och varje gång man vill ha tillstånd att exekvera så måste man lyckas ta en boll. Finns det ingen boll kvar i korgen måste man vänta till så är fallet. Semaforen är statisk, så alla trådar plockar bollar från samma korg, även om de inte hör till samma konkurrentadapter. Den är ock- så en “orättvis” semafor, så det är slumpen som avgör i vilken ordning väntande trådar får plocka bollar. Detta kan orsaka en temporär svältning av vissa konkurrentadaptrar, men påverkar inte slutresultatet då alla trådar kommer få exekvera förr eller senare. Som standard får konkurrentadaptrarna starta max 40 arbetstrådar totalt.

7. Logiklagret

Logiklagret är det som organiserar den insamlade datan och tillåter presentation av den för användare. DataHandler (7.1) sköter själva organisationen och hanteringen av produkter, och Server(7.2) tar hand om uppstart och lyssnande efter användare.

7.1. DataHandler

Det är DataHandler som agerar central nod i datahanteringen. Referensadaptrarna läg- ger till nya referensprodukter till referensproduktslistan via DataHandler:s addReferen- ceProduct(...), som i sin tur lägger till den nya referensproduktens produktnummer till alla konkurrentadaptrars arbetsköer (via AdapterHandler). När en konkurrentadapter behandlat produkten rapporterar den tillbaka en lista med CompetitorProduct:s (5.2) till AdapterHandler som lägger in alla konkurrentprodukter var för sig till rätt referen- sprodukt via metoden addCompetitorProduct(...).

Alla referensprodukter hålls i en java.util.concurrent.ConcurrentHashMap

23

. Det är en standardkomponent i Java sedan 1.5 som abstraherar bort behovet av att synkronisera en HashMap åt programmeraren. En HashMap i sin tur används för att koppla ett värde till en unik nyckel. Som nyckel används produktnumret och som värde används ReferenceProduct-objektet.

- Class Executors

23http://java.sun.com/javase/6/docs/api/java/util/concurrent/ConcurrentHashMap.html - 2011-05-25 - Oracles API för CHM

(15)

7.2 Server 7 LOGIKLAGRET

7.2. Server

Applikationen är som nämns i avsnitt 2 uppdelad i två delar; dels ett backend som utgörs av logiklagret och datalagret, och dels ett frontend som ger användarvänlig tillgång till backendet. Mer om frontendet i avsnitt 8 på sidan 17.

Server är en liten klass som tar hand om uppstarten av serverdelen av Pricer. Den behandlar först eventuella inparametrar och startar sedan upp en DataHandler (7.1) med avseende på givna inparametrar (eller standardvärden om inga inparametrar givits).

Därefter lägger den sig för att lyssna efter anslutningar från klienter. När en klient ansluter startar den en ny tråd med en instans av ClientHandler (7.3) och lyssnar sedan vidare efter nya klienter.

7.3. ClientHandler

När servern (7.2) tar emot en anslutning startar den en ny tråd med en instans av klienthanteraren ClientHandler i en egen tråd. Det är sedan den tråden som tar hand om att prata med den anslutna klienten. När en klient skickar ett kommando kommer klienthanteraren tolka det och returnera ett svar (för mer information om protokollet, se 7.3.1). Beroende på vilken sorts begäran det är kommer antingen ett fast svar ges (till exempel kommer “stop” alltid ge “goodbye” som svar) eller så genereras svaret dynamiskt.

Dynamiska svar genereras efter bästa förmåga genom att iterera över datamassan som efterfrågas. Det handlar antingen om DataHandler:s (7.1) productMap eller om failedMap, vilka båda är (som tidigare nämnts) av typen ConcurrentHashMap. När man itererar över en sådan garanteras man inte att få en fullständig bild. Det man garanteras att få är en bild som den sett ut någon gång mellan starten och slutet på iterationen.

Detta är dock inte riktigt ett problem om man vet om det eftersom man inte bör begära listning under en uppdatering, och när en uppdatering väl är klar är också innehållet i HashMap:en statisk (tills dess att en ny uppdatering startas).

7.3.1. Protokoll

Protokollet som används för att prata kommunicera mellan server och klient är textbaser- at. Detta gör att det är väldigt enkelt att testa servern utan att faktiskt ha en färdigskriv- en klient för man kan ansluta med ett vanligt program för rå textöverföring över nätverk (så som netcat

24

eller telnet

25

).

7.3.2. Funktioner

hello Svarar med ett “Hello there”. Används för att kontrollera att anslutningen mellan klient och server fungerar (en ping-funktion).

24http://nc110.sourceforge.net/ - 2011-05-26 - Netcat: the TCP/IP swiss army knife

25http://www.faqs.org/rfcs/rfc854.html - 2011-05-26 - RFC 854 - Telnet Protocol Specification

(16)

7.3 ClientHandler 7 LOGIKLAGRET

addid#produktnummer;produktnamn;referenspris;butiksnamn#produktnummer... Lägger manuellt till referensprodukter. Det går teoretiskt att lägga till hur många produk- ter som helst på samma rad. Dock får namnen inte innehålla varken nummertecken (#) eller semikolon (;). Detta är en känd brist, men då namnet endast är till för användarens bekvämlighet (produktnummer används för sökningar) så kändes det inte relevant att lägga tid på att fixa detta just nu.

update Startar en uppdatering. Denna sorts uppdatering kontrollerar endast konkur- renternas priser. Den egna produktkatalogen (referensprodukterna) uppdateras inte. Önskas det får man kalla på “forceupdate”.

forceupdate Stoppar en eventuellt pågående uppdatering, tömmer listorna med referen- sprodukter (productMap och failedMap, se 7.1 för mer information om dem) och startar en ny nerladdning av referensadaptrarnas mål. Detta har samma resultat som en omstart av servern, men utan att faktiskt behöva starta om den.

getid#produktnummer Returnerar en utskriven referensprodukt med givet produkt- nummer om en sådan finns, annars “null”. Se 5.1 om hur en referensprodukt skrivs ut som en sträng.

getall Returnerar alla referensprodukter, separerat med ny rad. Se 5.1 om hur en refer- ensprodukt skrivs ut som en sträng.

getcsv#referensnamn Returnerar en CSV-formaterad lista med alla referensprodukter som levereras av referensnamn. Inleds med en titelrad och avslutas med en tom rad. Denna funktion påminner till stor del om getall, men skiljer sig med att man i getall får alla referensnamn och deras priser medan getcsv kan man få bara ett.

Det går att få samtliga referensnamn om man inte lämnar något när man kallar på getcsv (alltså att man kallar på endast “getcsv”). Då skriver den ut titelraden och sedan kallar den internt på getall.

getfail På samma sätt som getall returnerar en lista på alla referensprodukter returnerar denna en lista med alla referensprodukter som ingen av konkurrentadaptrarna kunde hitta. Dessa finns inte med i listan man får från getall. En union av getall och getfail resulterar i en union av de produkter som lagts till av referensadaptrarna.

status Returnerar information om vad servern har för sig för tillfället samt statistik så

som hur många referensprodukter det finns för tillfället, hur många blivande refer-

ensprodukter som laddats ner av de respektive referensadaptrarna och hur många

som fortfarande väntar på att konverteras till referensprodukter. Nedan är ett ex-

empel på hur svaret från status kan se ut:

(17)

8 PRESENTATIONSLAGRET

Started: Thu May 26 13:11:56 CEST 2011

Currently have 19260 reference products and have failed to get information about 49 products.

The adapters:

Kunds-butik1: 15958 uniqe data elements added to workqueue, still adding. 261 waiting to be processed

Kunds-butik2: 19388 uniqe data elements added to workqueue, still adding. 127 waiting to be processed

Konkurrentkälla1: 476 work units handled this update round.

names Returnerar de olika referensnamn som är representerade bland referenserna, se- parerade med semikolon (;).

identify Saknar vettig funktion idag. Den finns här för att förbereda för den dag inlog- gning och kryptering eventuellt ska implementeras.

stop Stänger av servern.

quit Stänger anslutningen mellan server och klient utan att påverka servern. Svarar med ett “goodbye”.

8. Presentationslagret

Presentationslagret är det lager som möjliggör presentation av data för användaren.

Detta är Pricers frontend, det vill säga den del som är synlig utifrån.

8.1. ClientCommander

ClientCommander är ett interface som beskriver vilka funktioner som finns tillgäng- liga för en klient till Pricer. Den går hand i hand med NetworkThread (8.2), och en klass som vill använda sig av NetworkThread för kommunikation med servern måste im- plementera ClientCommander. De klasser som implementerar ClientCommander måste tillhandahålla metoden getNextCommand() som NetworkThread använder sig av för att hämta kommandon från dem. De får dock en synkroniserad kö via ClientCommander för det.

ClientCommander säger också att det ska finnas metoder för återkoppling efter att operationer utförts. Återkopplingsmetoder namnges på forment “callback[operation]”.

Till exempel så kommer NetworkThread efter att ha genomfört en statusförfrågan skicka svaret till metoden callbackStatus, och sen är det upp till implementationen att använda informationen.

Denna uppdelning mellan ClientCommander och en klass som implementerar den, till

exempel GUI (8.3), är gjord för att det ska vara enkelt att skapa nya användargränssnitt

utan att behöva skapa en ny nätverkshanterare. Antag till exempel att man vill skapa

ett program som automatiskt genomför en fast uppsättning operationer för användande

i script eller liknande.

(18)

8.2 NetworkThread 8 PRESENTATIONSLAGRET

Figur 3: Det grafiska gränssnittet

8.2. NetworkThread

NetworkThread tar hand om klientens kommunikation med servern genom att agera som en brygga mellan en implementation av ClientCommander och servern. Den tillhan- dahåller metoder som skickar korrekt formaterade strängar enligt protokollet beskrivet i 7.3.1. Svaret tas sedan emot och skickas till tillhörande återkopplingsmetod. Olika återkopplingsmetoder tar olika sorters inparametrar. Om svaret förväntas vara en kort textsträng används String, men om svaret förväntas vara en större textmassa används istället den upprättade anslutningen som inparameter och det är upp till implementa- tionen av ClientCommander att läsa av den. Till exempel skrivs svaret på “status” ut i GUI:et (8.3) medan svaret på “getcsv” skrivs direkt till en av användaren specificerad fil. Mer om det nedan.

8.3. GUI

I figur 3 visas en bild på det grafiska användargränssnittet (kort. GUI, för “Graphical User Interface”). Det är denna del av Pricer som användare interagerar med. Härifrån kan man på ett användarvänligt sätt utföra de (flesta) funktioner som finns listade i 7.3.2, till exempel hämta filen med data om konkurrenter.

GUI:et i sig kör inte i någon egen explicit tråd utan nöjer sig med den tråd man får som standard via grafikbiblioteket Swing. För att GUI:et alltid ska vara responsivt körs inga tidskrävande funktioner direkt därifrån. GUI implementerar ClientCommander och har därför en kö som nätverkshanteraren övervakar. Där lägger de knappar som startar tyngre funktioner kommandon till nätverkshanteraren (8.2) som utför dem i ordningen de läggs till i kön.

När en operation är klar kallar nätverkshanteraren på tillhörande återkopplingsme-

tod som behandlar resultatet av operationen på lämpligt vis. Resultatet av att kalla på

(19)

8.3 GUI 8 PRESENTATIONSLAGRET

“status” kommer till exempel uppdatera innehållet i gränssnittets statusflik (via call- backStatus(...)), men om man kallar på “getcsv#kunds-butik1” kommer svaret skrivas till filen som angetts i textfältet (via callbackGetCSV(...). Kontroller om huruvida filen är skrivbar sker i knappens funktionslyssnare innan operationen läggs till i kön eftersom

“getcsv” ofta är en tung operation som inte bör genomföras i onödan.

8.3.1. Layout

En viktig del i skapandet av grafiska gränssnitt är att få önskat innehåll att befinna sig på rätt plats. Det går antingen att använda sig av verktyg som låter utvecklaren mer eller mindre rita upp sitt gränssnitt eller så kan man göra det för hand. Båda har sina för och nackdelar, men en analys av det ligger utanför denna rapport.

Gränssnittet till Pricer är skrivet för hand då något tillfredsställande verktyg för att bygga det inte hittats. För att komponenter ska hamna på rätt plats använder man sig av en (eller flera) så kallad LayoutManager

26

. Till detta gränssnitt används en Layout- Manager som heter MiGLayout

27

. Den är ännu inte en standardkomponent i Java, men dess skapare jobbar för att den ska bli det i Java 1.7. MiGLayout är en mycket kraftfull LayoutManager som tar bort behovet av att nästla JPanels, något som annars brukar vara nödvändigt för att få gränssnitt att se ut som önskat. Det lämnas till läsaren att själv undersöka detaljer om MiGLayout då det ligger utanför rapportens omfång, men det kan varmt rekommenderas för att enkelt skapa snygga gränssnitt för hand.

26http://download.oracle.com/javase/tutorial/uiswing/layout/index.html - 2011-05-31 - Lesson: Laying Out Components Within a Container

27http://www.migcalendar.com/miglayout/ - 2011-05-25 - MiGLayout - The Java Layout Manager for Swing, SWT and JavaFX

(20)

9 FÖRBÄTTRINGSPOTENTIAL

Del III.

Diskussion och slutord

9. Förbättringspotential

Efter att ha genomfört projektet anser jag att lösningen i det stora hela blev bra. Det finns dock ett antal saker jag skulle göra annorlunda om jag gjorde om projektet igen.

Här följer de största problemen och potentiella lösningar på dem.

9.1. Arbetsminne

Pricer äter arbetsminne. Efter att första uppdateringen är klar dra den ungefär 2,5GB.

Detta kommer sig av att Java i sig är resurshungrigt tillsammans med att det är väldigt mycket data som indexeras. Jag borde ha jobbat hårdare på att hålla minnesanvänd- ningen nere, men jag försvarar mig med att jag fick felaktig information om mängden produkter som skulle indexeras. Från början fick jag veta att det handlade om ca 100 000 produkter som skulle indexeras, en uppgift som även min bärbara dator klarade av bra.

Det visade sig dock senare att det fanns ca 600 000 produkter till som skulle indexeras.

Detta gjorde att minnesförbrukningen vid en körning rusade. Den nuvarande lösningen på problemet är helt enkelt att köra på en större och snabbare server. Inte en optimal lösning, men den fungerar i brist på bättre.

För att förbättra minnesanvändningen skulle jag rekommendera att skriva ner inläst data till en fil eller databas. Man skulle också kunna skriva om strukturen på ett sätt som nyttjar färre objekt. Just nu innehåller varje referensprodukt en HashMap med ProductPrice (5.3) och konkurrentdata (5.2) lagras i både en HashMap och en TreeSet.

Notera att det är samma objekt med konkurrentdata som pekas ut av båda samlingarna.

Det var nödvändigt att göra så för att både ha konkurrentdata sorterat på pris men ändå försäkra sig om att inga dubbletter uppstår. Det är förmodligen där man ska börja optimeringen.

9.2. Styrklasserna

Efter att implementationen är färdig känner jag att det blev lite väl många styrklasser.

Framför allt hade AdapterHandler helt kunnat sammanfogas med DataHandler för att minska antalet metodkall när man söker vilket ger bättre prestanda och minskad kom- plexitet. Även Server hade nog kunnat sammanfogats med DataHandler, men i det fallet är det lättare att försvara en uppdelning då Server har en unik uppgift.

9.3. Prestanda

Som nämns i 6.4 på sidan 13 så används 40 trådar för hämtning av konkurrentdata.

Denna siffra är framtagen med hjälp av ett inte särskilt vetenskaplig test. En körning

startades och när systemet kommit igång klockade jag hur många arbetsenheter (där

(21)

9.4 Konfigurationsfil 9 FÖRBÄTTRINGSPOTENTIAL

Antal trådar Antal arbetsenheter (30 sek)

20 1716

30 2370

40 2419

44 2842

50 2367

Tabell 1: Lista över antalet arbetsenheter som behandlats på 30 sekunder

en arbetsenhet är att hämta konkurrentdata för en referensprodukt) som behandlades på 30 sekunder. Siffrorna i tabell 1 kommer från körningar vid samma tillfälle, men det är bara en körning var, så felmarginalen är hög. Jag var dock inte ute efter ett perfekt prestandatest utan snarare ett riktvärde, vilket det fungerar som. Anledningen till att 40 trådar används som standard och inte 44 (som presterade mycket bättre i testet) är att när jag försökte med det sänkte det mitt nät efter en liten stund. Jag kommer troligen ändra standard till 44 efter att ha försäkrat mig om att nätet där servern kommer köra tål det.

En enkel förbättring som borde göras till detta projekt är att lägga in stöd för att välja, eller kanske till och med dynamiskt styra, det maximala antalet arbetstrådar. Att statiskt sätta övre gränsen för trådar (alltså vid uppstart av servern) skulle vara ett enkelt ingrepp genom att lägga in en till inparameter.

För att styra den dynamiskt skulle det krävas ett lite intressantare ingrepp. Jag har en (otestad) teori om att man skulle kunna lägga in en tråd som kontrollerar hur många arbetsenheter som behandlas per tidsintervall, och sedan testa att öka antalet trådar och testa igen. Så länge prestandan ökar kan man höja gränsen för arbetstrådar, och skulle den sjunka kan man alltid sänka gränsen igen. Jag tror det vore en intressant funktion som troligen skulle behöva en hel del pyssel för att få rätt.

9.4. Konfigurationsfil

För att öka skalbarheten i Pricer skulle det behövas en lösning för att välja vilka adaptrar som ska användas. Man kanske skulle vilja lägga till en konkurrent eller ta bort en referens. För närvarande är det hårdkodat i DataHandler (7.1) vilka adaptrar som ska laddas, vilket inte alls är dynamiskt. Just nu finns det inte ett behov av att ladda andra adaptrar än de som är hårdkodade, men det är helt klart något som bör göras i framtiden.

Då funktionen att dynamiskt välja vilka adaptrar som ska laddas via en konfiguration

har funnits i åtanke genom hela projektet, men aldrig fått prioritet, så är det i alla fall

förberett. Adaptrar kan exporteras till egna jar-filer. Det vore inte svårt att lägga in en

funktion för att läsa in sökvägar till adaptrar och ladda dem.

(22)

10 SLUTORD

10. Slutord

Jag är ganska nöjd med hur produkten blev i slutändan. Trådningen snabbar upp över- föringarna på ett bra sätt och den resulterande listan är precis lagom informativ. Det jag är minst nöjd med är hur mycket arbetsminne den förbrukar för att hålla all data i minnet. Det är dock svårt att komma från, det är väldigt många produkter som ska indexeras.

Jag vill tacka alla som på ett eller annat vis hjälpt mig med denna rapport, men

framför allt min handledare Fredrik Lundevall och min kund. Utan dem hade det inte

gått att genomföra detta projekt. Jag vill också tacka alla i min omgivning som i tid och

otid blivit lurade att korrekturläsa denna rapport, i synnerhet Patrik Dahlström. Det

har varit mycket hjälpsamt med era åsikter.

(23)

10 SLUTORD

App endix Figur 4: Klassrelationer inom Pricer

References

Related documents

Sedan urminnes tid har folk sysslat med mätning av olika slag. Tid, längd, höjd, area, energi, volym och styrka är några exempel på vad som behövde mättas. Att mäta är

In order to achieve adequate pricing there has to exist some steering of the price levels based on the value for the customer, what the company’s product or service may add as

Då jag själv var både den som dokumenterade och ledde testet finns det saker som jag kan ha missat då jag behövde fokusera på mer än en sak ifall jag hade

Konsumenten har vidare under vissa förutsättningar rätt till begränsning av behandling av sina personuppgifter, rätt till radering av personuppgifter, rätt att invända mot

Utöver ovanstående är elhandelsföretaget skyldigt att i samband med att insamlingen av personuppgifterna sker lämna information om identitet och kontaktuppgifter för

En företrädare för kunden har rätt att begära information om personuppgifter om denne som behandlas av elnätsföretaget samt även begära rättelse.. Företrädaren har vidare

Företrädaren har vidare under vissa förutsättningar rätt till begränsning av behandling av sina personuppgifter, rätt till radering av personuppgifter, rätt att invända

Sequence Diagram As aforementioned, a sequence diagram is used for mapping with the corresponding behavioral Rebeca concepts that are defined as a part of the message servers and