webForumDet fria alternativet

Arkitektur (städdags)

.NETur .NET

61 svar · 2 845 visningar · startad av rhdf

Medlem sedan okt. 2007446 inlägg
Frågan#1

Jag tänkte sätta mig och börja sanera upp i koden på ett av mina små "hobbyprojekt", en webbshop. I dagsläget så kör ett par sidor olika versioner av den, men jag tänkte nu sätta mig och äntligen bygga "kärnan" i systemet så generiskt som möjligt.

Nåväl
Om man tar en så pass enkel(?) sak som en produkt.
idag har jag en funktion, GetProductByID i klassen ProductsBLL, den Returnerar ett objekt av typen Product. I samma klass finns funktionen GetProductListByCategoryID (långt namn, men rätt uppenbart iaf), som returnerar en list<T> (om man skall exponera listor diskuteras ju i en annan tråd). Product används även som bas för bla OrderItem(som borde heta nånting annat) som ju faktist ÄR en produkt bara det att man vill ha med ett antal.
Borde jag ändra här och ha en basklass som både "shop-produkt" och "orderItem" ärver ifrån ?

Det har visat sig att den generella "Produkt" jag har ibland kräver lite extra properties vid olika tillfällen, tex en shop vill ha med ett extra infofält, medans nästa vill ha med tillverkare+ namn+modell i separata fält osv

Produkt är ju i stort sett bara en databärare, så vad borde klassen som hanterar hämtning av en eller flera heta och vad bör den innehålla för metoder?

just nu använder jag ingen ORM utan kör med SQL + ett kasst skrivet DAL som egentligen är åt helvete kodat (tror jag)
DAL innehåller tex metoder för att skapa/hämta en data-adapter, en reader och en för att exekvera Update/Delete/Insert frågor

I slutänden skulle jag vilja få till nånting så man kan köra:
yadda.datasource=Products.GetProductByID(42)
och
Products.AddProduct(new Product(......)) eller nåt sånt iaf

Lite sparkar i rätt Riktning tack *väntar på att Normén ,Gladh mfl skall komma igång :)

Medlem sedan aug. 20003 575 inlägg
#2

Det du kallar Products i slutet tycker jag borde heta ProductRepository, annars tycker jag dina metodnamn är ganska okej, men du kanske kan ta bort ordet Product ur dom då du redan specifierat att det är products som din klass hanterar genom att kalla klassen just ProductRepository :).

Om du är ute efter ett ORM så rekommenderar jag fortfarande NHibernate som jag alltid gör, men har du redan ett bra som fungerar så varför inte fortsätta med det?

OrderItem tycker inte jag skall ärva av Product utan OrderItem tycker jag skall innehålla antal som du säger, ett pris, ett datum och en kopia av en produkt, vad mer den bör innehålla vet du bäst själv :).

Olika lösningar för olika shoppar Produktobjekt för varje shop, alternativt låta product objektet innehålla all data som behövs för varje shop och de tredje alternativet ha en PropertyBag (vilket dock kan vara den sämsta lösningen).

Medlem sedan sep. 200888 inlägg
#3

rhdf skrev:

Borde jag ändra här och ha en basklass som både "shop-produkt" och "orderItem" ärver ifrån ?

Det har visat sig att den generella "Produkt" jag har ibland kräver lite extra properties vid olika tillfällen, tex en shop vill ha med ett extra infofält, medans nästa vill ha med tillverkare+ namn+modell i separata fält osv

Innan jag svarar på citatet här så tänkte jag bara ta upp detta med namn.

Namnen i sin applikation skall helst heta det kunden pratar om. Och innehålla just det kunden refererar till. Såg att du kalla din typ BLL eller DAL komponent för Products. Det är inget fel på detta, dock tror jag du kommer till ett läge då detta namn har en annan betydelse och på så vis blir vilseledande.
Jag gillar DDD (Domän Driven Design) mkt för just hur man skall tänka och även ta del av de typ notationer som finns där för olika klassers ändamål.
Jag gillar inte ord som BLL eller DAL det får mig att rysa... :-)

Eniteter är typ de databärande klasserna, dessa bör ha namn som speglar det kunden pratar om. Ex Product, Order etc... Dessa entiteter får gärna ha businesslogik i sig. MEN OBS! när jag säger businesslogik här menar jag saker som mer eller mindre bara hanterar entiteten och eller dess aggregats tillståndsdata. Dvs man pratar aldrig mot andra klasser så som service klasser, dataaccess klasser m.m.

Businesslogik som kräver att man måste göra detta lägger man helst i så kallade service klasser. De brukar oftast vara en tjänstehanterare av en entiteter typ.

För datahämtning och lagring använder man Repositories. Dessa kan ses lite som typ DAL fast ändå inte. Deras uppgift är att ge dig fyllda entiteter och eller hantera hur dina entiteter sparas mot datakälla. De metoder dessa skall ha är mer eller mindre motsvarande where satser typ. Lite som du gjort.

GetProductByID(....), GetProductByCategory(....) etc...
Det är i dessa du exempelvis kan använda ORM om du vill och eller manuell mappning. Med manuell mappning menar jag typ att du använder SQLDataReader för hämta datat och skriver koden själv för att mappa datat mot den/de entiteter du vill returnera.

Så nu till citatet eller dina frågor.
Ex ShoppingCart och Order kan kanske ha likasinnade properties, men bara för det tycker jag inte man skall ärva en gemensam basklass av många skäl. Men för att inte gå in på dem för tekniskt så är det så att dessa två saker är faktiskt två olika saker i ens domän. Arv här är bekvämt men fel bekvämlighet då det istället skapar andra problem. Dvs businesslogiken kan skilja, en ShoppingCart är inte en Order, dock blir den till en Order.

I OO skall helst arv användas i is förhållanden. Ex. Teacher is Employee

Mvh Johan

Medlem sedan okt. 2007446 inlägg
#4

johannormen skrev:

Så nu till citatet eller dina frågor.
Ex ShoppingCart och Order kan kanske ha likasinnade properties, men bara för det tycker jag inte man skall ärva en gemensam basklass av många skäl. Men för att inte gå in på dem för tekniskt så är det så att dessa två saker är faktiskt två olika saker i ens domän. Arv här är bekvämt men fel bekvämlighet då det istället skapar andra problem. Dvs businesslogiken kan skilja, en ShoppingCart är inte en Order, dock blir den till en Order.

I OO skall helst arv användas i is förhållanden. Ex. Teacher is Employee

Mvh Johan

Enligt det sista där så är det alltså "ok" att hävda att en orderrad är en produkt
i min värld( som inte behöver vara korrekt:) ) så består en order av produkter
Vid flera tillfällen i en webbshop så vill man ju ha möjlighet att få fram diverse data om en produkt, utifrån just en order
Det känns då lite dumt att ha en kopia på product, med ett tillägg för antal

Shoppingcart och order är jag helt med på att de är olika saker, eftersom en kundvagn bara innehåller en lista av produkter Som kunden _kanske_ tänker köpa, en order innehåller ju information om ordern i sig + en lista av orderrader ( + en kund )

I min klass "product" så har jag knappt någon logik alls bara en jäkla massa properties (rester från senaste shoppen där varje produkt hade en massa data), på pappret så behövs väl egentligen bara
(Tillverkare)
Titel
Pris
Infotext
Bildreferens
(och en nuffra för lagerstatus)
har jag missat nåt?

Medlem sedan maj 20012 812 inlägg
#5

rhdf skrev:

Det har visat sig att den generella "Produkt" jag har ibland kräver lite extra properties vid olika tillfällen, tex en shop vill ha med ett extra infofält, medans nästa vill ha med tillverkare+ namn+modell i separata fält osv

Det är därför som det iprincip är omöjligt att få en generell productklass som alla kan använda. Och det gäller så många andra klasser i sammanhanget, typ kunder, olika funktioner i affärslogiken för olika företag osv osv..

Så mitt tips blir att istället fokuser på saker som du faktiskt kan dela över flera kunder (med väldigt små förändringar, eller inga alls) om vi tar din shoppingcart så kommer den till 99% vara lika för alla kunder (kanske inte utseende mässigt, men funktionsmässigt) den kan du alltså kapsla in i en egen assembly och använda dig av för att hantera shoppingcart för alla dina kunder. Problemet blir då att du måste skicka in en produkt till denna och produkt kommer att vara olika för olika webshopar, så du skickar helt enkelt inte in Product till ShoppingCart utan givetviss IProduct. Där du definerar upp exakt vad som behövs av en produkt för att ShoppingCart skall vara glad och nöjd.

På det visset så kommer du kunna återanvända en del av din kod, medans massor kommer bli specifikt för respektive kund.

- M

Medlem sedan maj 20012 812 inlägg
#6

rhdf skrev:

Enligt det sista där så är det alltså "ok" att hävda att en orderrad är en produkt

En orderrad kan aldrig bli en produkt, de har inget som helst gemensamt. En orderrad kan innehålla en produkt, men inte ärva från den.

Det skulle vara samma sak som om du säger att en kund kan ärva från en adress bara för att kunden råkar bo någonstans. Kund och Adress är helt skilda saker och kan inte ärva av varandra, men de kan däremot vara beroende av varandra och ha relationer.

- M

Medlem sedan sep. 200888 inlägg
#7

Gladh skrev:

rhdf skrev:

Enligt det sista där så är det alltså "ok" att hävda att en orderrad är en produkt

En orderrad kan aldrig bli en produkt, de har inget som helst gemensamt. En orderrad kan innehålla en produkt, men inte ärva från den.

Det skulle vara samma sak som om du säger att en kund kan ärva från en adress bara för att kunden råkar bo någonstans. Kund och Adress är helt skilda saker och kan inte ärva av varandra, men de kan däremot vara beroende av varandra och ha relationer.

- M

Håller med Gladh och Nicke här. En produkt är en enhet en orderrad kan vara flera och innehålla flera produkter.

Jag brukar ex på min orderrad ha quantity och produkt som proppar. Detta för att raden skall veta hur många av produkten jag vill ha. Detta skall inte en produkt i sig veta.

Sen kan man ju adda samma produkt flera ggr och hävda att man då vet hur många av samma man vill ha, problemet är att då har du inte samma produkt instans utan kopior av den och på ett OO sätt blir det som att ha olika produkter men råkar heta samma typ.

Mvh Johan

Medlem sedan okt. 2007446 inlägg
#8

Den Kundvagnslösning jag har idag består av 2 klasser
Eller egentligen 2 interfaces + en herrans massa klasser
ICartItem
IShoppingcart
Varför det är gjort så vet jag inte riktigt(hittade den på typ codeproject och sen har den fått hänga med)
men den är visst tänkt att vara "generisk" så att man kan välja hur kundvagnen skall sparas, dvs cookie,session eller databas

det som skickas "in" i kundvagnen är då ett st cartItem som innehåller
ProduktID
Antal
UnitPrice

Skulle det vara bätte att ha en product som propp istället för productID här?
fast det blir ju inget snyggt ;) eller?
CartItem.Product.productID=278
CartItem.Product.UnitPrice=99
CartItem.Quantity=2
Shoppingcart.Add(CartItem)

Som Gladh skrev: jag "måste" ju då ha en Iproduct där eftersom min kundvagn skall slippa bry sig om hur min product för just den shoppen ser ut

Sen skall ju "skiten" UT med, men då antar jag att det är lämpligt att returnera en list<cartItem> (eller nåt annat bra)

I Cartklassen har jag dessutom i dagsläget lite för mycket databasanrop för att jag skall vara nöjd. Borde jag ha en 3:e klass i den assemblyn som fungerar som repository? eller skall jag låta ShoppingCartRepository ligga utanför assemblyn?

Medlem sedan okt. 200850 inlägg
#9

rhdf skrev:

CartItem.Product.productID=278
CartItem.Product.UnitPrice=99
CartItem.Quantity=2
Shoppingcart.Add(CartItem)

Frågan är om du inte istället kan lägga till en Produkt med en gång i din vagn istället för en CartItem.. Allt beror på din modell. Men som jag ser det så stoppar man produkter i kundvagnen. Även om du stoppar samma typ av produkt, så är dom inte samma produkt. Så jag skulle haft följande API på min ShoppingCart:

Add(IProduct product)

Skälet till IProduct och inte Product, är pga av LSP Principe. Fördelen med Interface dirven utveckling är att intrefacet skapar ett kontrakt mellan dig och användaren. I .Net 4.0 så kommer vi få bra stöd för Design By Contract som underlättar att vi inte bryter mot LSP. Se till att din Product klass i sealed ifall du inte vill att någon ska ärva från den. På så sätt kan du också undvika att bryta mot LSP.

Jag skulle sedan ha en property vid namn Items som returnerar en ReadOnlyCollection<T>, jag skulle gärna velat ha ett IReadOnlyCollection<T> interface, men tyvärr har inte Microsoft något sådant. Skrev precis ett blogginlägg om List<T>:

http://weblogs.asp.net/fredriknormen/archive/2008/11/02/don-t-return-list-lt-t-gt-from-a-public-member.aspx

Om du låter dina entiteter hämta och uppdatera dess data, så använder du dig av ett pattern som heter Active Record, behöver inte vara något fel med det men det är att föredra att skapa entiteter som inte har massa dependency till underliggande "lager". Så i ditt fall för för att vara persistent ignorant, så kan du använda dig av Repository pattern. Repositories hör till ditt domän lager, så du kan låta det ligga i samma assembly som dina entiteter, detta för att ev undvika cirkulära referenser.. jag brukar skapa två mappar i mitt Class lib. projekt för min domän lager, Entities och Repsitories. Men det är en smaksak hur man vill ha det. Kom ihåg att ALLTID skapa ett interface för dina Repositoties för att underlätta skapandet av Stubbar vid enhetstestning.

http://martinfowler.com/eaaCatalog/repository.html

Medlem sedan maj 20012 812 inlägg
#10

fredrikn skrev:

Repositories hör till ditt domän lager, så du kan låta det ligga i samma assembly som dina entiteter, detta för att ev undvika cirkulära referenser..

Nja... Låt ditt IRepositrory interface ligga i samma assembly som dina entiteter (jag har lagt mina i samma assembly som implementerar domain-servicerna), men implementera Repositoryt i ett eget assembly. Blir så mycket enklare om du vill byta ut ditt repository i ett senare skede (fast hur ofta händer det...). Och så jobbar du bara mot IRepository i din applikation.

- M

Medlem sedan okt. 200850 inlägg
#11

Gladh skrev:

Nja... Låt ditt IRepositrory interface ligga i samma assembly som dina entiteter (jag har lagt mina i samma assembly som implementerar domain-servicerna), men implementera Repositoryt i ett eget assembly. Blir så mycket enklare om du vill byta ut ditt repository i ett senare skede (fast hur ofta händer det...). Och så jobbar du bara mot IRepository i din applikation.

Repositories tillhör modellen.. därför lägger jag entiteter och Repositories i samma assemblies. Service/Application layer ska inte ha några Repositories.

Skulle Repositories bytas ut, så innebär det en ny implementation och om vi ska byta ut alla så kan vi lika väl ändra på dom vi redan har. Ett annat alternativ är att dölja dom med Factories, då spelar det inte så stor roll vart vi har dom, jag använder mig ofta av factories när jag kör med interface driven utveckling.

Om vi skulle välja att köra med Lazy Load genom att entiteterna anropar repositories och vi har dom i två separata assemblies, så kan det leda till en cirkulär referens. Iofs hjälper OR-mappers till med lazy load, om vi så anser att vi skulle vara behov av det. Om vi inte vi använder en OR-mapper med lazy load stöd, så får vi sköta det manuellt..enklast är att anropa Repositories från en entitet, men det bör undvikas. Bättre att använda sig av en proxy.

Medlem sedan maj 20012 812 inlägg
#12

fredrikn skrev:

Repositories tillhör modellen.. därför lägger jag entiteter och Repositories i samma assemblies.

Jag ser mer att mina respositories tillhör infrastrukturen och inte själva modellen. Men det kanske kan diskuteras utifrån hur repositoryn ser ut. Om du kör med Repository -> DAL -> ORM, så skulle man ju kunna hävda att DAL:et är själva infrastrukturen, men om du kör med Repository -> ORM så tycker iallafall jag att det är Repositoryt som bestämer hur data skall hämtas och lagras till datakällan, och då tycker jag att de tillhör infrastrukturen och inte modellen. Så som "Onion Archtiecture" beskriver det. (http://jeffreypalermo.com/blog/the-onion-architecture-part-1/)

- M

Medlem sedan maj 20012 812 inlägg
#13

fredrikn skrev:

Jag skulle sedan ha en property vid namn Items som returnerar en ReadOnlyCollection<T>, jag skulle gärna velat ha ett IReadOnlyCollection<T> interface, men tyvärr har inte Microsoft något sådant. Skrev precis ett blogginlägg om List<T>:

Det håller jag med om. Man skall dock tänka på att om man väljer att köra med Databinding av sina kontroller i UI:et så kan listan ibland vilja ha mer rättigheter än bara att läsa. Det går ju att koda sig runt det, men ibland är det helt enkelt smidigast att lägga en item direkt till listan, speciellt när man använder färdiga gridviews och sådant där man kan högerklicka och välja "New...".

Problemet blir hur man gör med validering av data, eller om man vill använda sig av Factories. Hade just det problemet i en WPF-applikation där lösningen inte blev speciellt snygg, men det fungerar och det är huvudsaken. Problemet är ju att DataBinding inte är byggt för att fungerar med DDD, utan att passa MS klicka-gissa-spring sätt...

- M

Medlem sedan okt. 200850 inlägg
#14

Gladh skrev:

fredrikn skrev:

Repositories tillhör modellen.. därför lägger jag entiteter och Repositories i samma assemblies.

Jag ser mer att mina respositories tillhör infrastrukturen och inte själva modellen. Men det kanske kan diskuteras utifrån hur repositoryn ser ut. Om du kör med Repository -> DAL -> ORM, så skulle man ju kunna hävda att DAL:et är själva infrastrukturen, men om du kör med Repository -> ORM så tycker iallafall jag att det är Repositoryt som bestämer hur data skall hämtas och lagras till datakällan, och då tycker jag att de tillhör infrastrukturen och inte modellen. Så som "Onion Archtiecture" beskriver det. (http://jeffreypalermo.com/blog/the-onion-architecture-part-1/)

- M

Ur ett DDD perspektiv så tillhör Repository modellen. Många vill gärna jämföra Repository med DAL för att lättare förklara vad Repsoitory är för en utvecklare. Men Repository är bara en förvaringsplats av entiteter. Sedan kan Repository använda sig av ett DAL, vi kan även se vissa delar i Infrastrukturen som gör data access som DAL. Begreppet DAL finnt inte inom DDD. Men frågan är om det verkligen är viktigt i vilken assembly vi lägger våra Repositories?, jag anser inte att det viktigt att ha dom i en egen assembly, men det går om vi känner oss mer bekväm med det.

Medlem sedan jan. 20022 440 inlägg
#15

Det beror väl på lite. Har du flera projekt som ska dela repositories men inte behöver resten av repository assemblyn kan det vara vettigt att lyfta ut den. Annars håller jag med. Tycker faktiskt det är jobbigare att bläddra bland olika assemblyn (projekt) än att bläddra i mappar i samma :)

Medlem sedan okt. 200850 inlägg
#16

CatZ skrev:

Det beror väl på lite. Har du flera projekt som ska dela repositories men inte behöver resten av repository assemblyn kan det vara vettigt att lyfta ut den. Annars håller jag med. Tycker faktiskt det är jobbigare att bläddra bland olika assemblyn (projekt) än att bläddra i mappar i samma :)

Om du kör med Domain Driven Design så kommer du inte vilja återanvända dom i andra projekt om du inte har exakt samma domän modell i dom, men det kommer du till stor sannolikhet inte att ha.. det är komplicerat att återanvända domän modeller i olika projekt, delvis pga att du skapar entiteter beroende på context.

Det är lätt att trilla tillbaka på Windows DNA tänket där vi har PL, BLL och DAL som separata lager. Kör man med den typ av arkitektur, så har du lättare möjligheter att återanvända.

Medlem sedan sep. 200888 inlägg
#17

Gladh skrev:

fredrikn skrev:

Repositories hör till ditt domän lager, så du kan låta det ligga i samma assembly som dina entiteter, detta för att ev undvika cirkulära referenser..

Nja... Låt ditt IRepositrory interface ligga i samma assembly som dina entiteter (jag har lagt mina i samma assembly som implementerar domain-servicerna), men implementera Repositoryt i ett eget assembly. Blir så mycket enklare om du vill byta ut ditt repository i ett senare skede (fast hur ofta händer det...). Och så jobbar du bara mot IRepository i din applikation.

- M

Jag brukar lägga repositories i egna projekt. Mkt pga Mocking där jag vill använda fakes i vissa lägen. O självklart en RepositoryFactory som ger mig mina repos av önskad typ..

Medlem sedan maj 20012 812 inlägg
#18

johan skrev:

Jag brukar lägga repositories i egna projekt. Mkt pga Mocking där jag vill använda fakes i vissa lägen. O självklart en RepositoryFactory som ger mig mina repos av önskad typ..

Johan vi tänker mer och mer likadant... hur skall det här sluta.

Din RepositroyFactory skapar du de själv med typ latebinding, eller använder du något injection-framework? Jag har tittat på några olika framework, men tycker ärligt talat att de komplicerar mer än att bara skapa egen RepositoryFactory, men latebinding, där man kan skicka in en parameter om det skall vara mock eller äkta repositorys som man skall använda sig av.

Jag kanske inte har gett frameworken tillräckligt med tid för att inse fördelarna!

- M

Medlem sedan okt. 200850 inlägg
#19

För att märka ord och vara märkvärdig ;) Så är inte ett "fake" Repository en mock utan en stub.

Det är stor skillnad på hur man hanterar stubbar och repo i små system och stora enterprise apps. I stora enterprise så kan man vilja vid olika testtillfällen förändra fake datan.. detta kan man tex göra genom att låta stubbar har egna members för att enkelt lägga till testdata. Om man väljer den lösningen så finns det inget jättebra Factory ramverk som hjälper till där.. iofs finns det, där vi kan specifiera testdatan i form av XML.. men min erfarenhet är att det är mycket enklare att själv initiera sina Repo stubbar och sätta data i kod och sedan skicka in dom. tex:

var customerRepository = new StubCustomerRepository();

customerRepository.AddCustomer(new Customer(...));
//...

var customerService = new CustomerService(customerRepository);

//Mina tester -- Assert

Det som man bör tänka på är att vissa Services eller Fasader behöver kunna ta in flera stubbar och vanliga Repo.. så för att samla dom kan du tex skapa en "context" klass.. har ett gammalt blogginlägg om detta: http://fredrik.nsquared2.com/ViewPost.aspx?PostId=364 dock inte om Repo, men tanken är det samma.. och order Mock där i bloggen är helt fel.. det va innan jag blev vis ;)

Nu är inte jag Johan men tänkte ändå ge förslag på DI ramverk som funkar mkt bra även som Factories.. Jag har använt Spring.Net för detta och tycker det har funkat väldigt bra.. Sedan finns nu Unit från Ent Lib 4.1, vi har Windsor container, som många gillar.. sedan finns det fler.

Bara en sista sak om Repositories och dess placering.. Som jag skrev tidigare så går det bra att lägga Repositories i egna Assembly, men erfarenhet i stora system där inte OR-mappers har använts pga policies etc, så har det ofta uppkommit cirkulära referenser när man har valt att lägga dom separat från samma Assembly som sina entiteter. Jimmy Nilsson som är mr DDD him self, lägger ofta sina Repositories i samma assembly som sina entiteter, även andra som jobbat mycket med DDD gör på samma sätt.. Att samla allt som berör ett layer i en assembly ger många fördelar, en av dom är att man slipper en massa projekt drösande i sin solution explorer, och mindre .dll vid deployment.

Om Factories används för att skapa Repositories, se till att dom använder sig av singelton pattern, finns ingen annledning till att skapa upp nya instanser av en Repo för varje gång man vill använda sig av dom.

Medlem sedan sep. 200888 inlägg
#20

fredrikn skrev:

För att märka ord och vara märkvärdig ;) Så är inte ett "fake" Repository en mock utan en stub.

Vem sa att det var det? Hittar inte ;)

Vet inte varför det skall bli så många projekt för att man har ett extra för sina repos. ett mer eller mindre ;-) fördelen är ju att domänen kan vara lite fri o leva separat. Se mer mot slutet av Evans bok om just återanvända domäner m.m.

Men smaken är som baken och jag tror inget är mer rätt än fel...

Mvh Johan

148 ms totalt · 3 externa anrop · v20260731065814-full.b746b907
0 ms — hämta forumlista (cache)
0 ms — hämta statistik (cache)
145 ms — hämta tråd, inlägg och bilagor (db)