Under några dagar har jag testat att göra min första simpla Silverlight applikation. Jag har också försökt utgå från bland annat Domain Driven Design och Test Driven Development. Det skulle vara intressant att få feedback på främst DDD-delen av applikationen. Jag är ingen expert i området men vill gärna lära mig mer. Oj... Kolla lite på hur CodeCampServer är byggt (inte helt klockrent överallt men det borde ge lite bra uppslag på lagerindelning, namnsättning osv) Jag tror många kan missuppfattar vad TDD är. Instämmer, använde mest termen TDD för att Krister nämnde i posten att kan körde DDD och TDD. Jag skulle vilja lägga till ett steg i listan Helt rätt... --> Johan Normén & Andreas Öhlund Tja.. Krister: Andreas: "vissa saker är intresanta så som användning av IQueryable," "men helt plötsligt har man knutit ihop sin datakälla väldigt mycket hårdare mot sin modell än önskvärt... " "Om du skickar upp och runt IQueryable så sprider du ut frågeskapandet och tappar kontrollen över var och när frågorna skapas. Då kan du nästan lika gärna bygga T-SQL i gränssnittet." @Magnus: "Rob flyttar problemet till sina "Services", dessa har massa metoder istället." @Magnus: Mangus: Reporsitory pattern bygger separation of concerns, eller single responisibility principle, en modul skall bara ha en anledning att förändras genom att ta upp query logik (vilket LINQ är) upp i service lagren så bryter man mot det, man inför också en code smell som Fowler kallar Shotgun Surgery eller solution sprawl vilket innebär att man sprider ut ett ansvarsområde över hela lösningen. @Fredrik "Jag vet, men det är så otroligt irriterande att skriva 17 olika metoder för att hämta data på 17 olika sätt. Det hade varit så himla smidigt att bara skriva som jag skrivit... men tyvärr är ju nackdelarn lite för många... " "Fast du måste ändå skriva kod 17 ggr för hämta på 17 olika sätt... :-) Fördelen är att du nu har ett ställe där du gör det än utspritt i koden på massa olika ställen. " Magnus: @Magnus @Magnus: Kollade på Specifications patterns och visst så får jag mindre antal metoder, men som påpekades i artiklen (eller någon länkad artikel) så är problemet att "omvandla" inputdatan till att bli ett WHERE statement i SQLSatsen.Feedback på DDD-delen i applikatione
Du finner koden och spelet på följande address:
http://kauppi.levasunt.nu/2008/08/13/minnesspel-i-silverlightSv: Feedback på DDD-delen i applikatione
Inte många rätt där mer än vissa namn :-)
Men ett bra försök.
Orkar inte gå genom allt.
Först bara en smaksak det är namngivelsen. BLL o DAL... Dessa namn gillar jag inte. Då denna indelning inte direkt existerar längre. Glöm allt vad MS DNA heter o hur de gör och vad de sagt att man skall göra.
Sen bryter du mot lite principer, dock inte direkt specifikt för DDD men ändå viktiga att ta del av tycker jag.
Ex Så gör du en Add (en command metod) i en av dina Repositories och returnerar ett värde. Kolla inte så noga varför men reagerade genast på detta.
Dina repositories kan ligga i eget projekt (liten detalj bara inget måste) men skall man ha dem någon stans i ett projekt som inte bara är deras så bör de i alla fall läggas i Domän projektet, som jag dock inte kunde se att du hade. BLL kanske var ett försök?
Ditt DAL gilla jag inte. Men det har vi pratat om förut :)
Men i övrigt tycker jag du har en ok struktur, det var lätt att hitta i koden. var ju i ofs rätt liten kod...
Sorry om det var lite hård kritik... Synd bara att jag inte har tid att göra den mer konstruktiv...
Lägg in mig på msn: johan.normen@home.se så kan vi tjata mera... Lättare så...
Har du läst DDD boken? För tror det är lättare då än att försöka översätta klassiska N-Tier till det man tror är DDD.
Mvh JohanSv:Feedback på DDD-delen i applikatione
http://code.google.com/p/codecampserver/source/browse/trunk/src/
En liten TDD grej som jag såg:
Försök att namnge dina test och testmetoder så det blir lite lättare att förstå vad dom faktiskt testar.
Om vi exempelvis har en Domain Service som heter CustomerService och bla. skall tillåta oss att uppgradera kunder till olika nivåer (Platina, Gold, osv) så skulle man kunna namnge testklass och testmetod ungefär så här:
[TestFixture]
public class The_customer_service_should
{
[Test]
public void Enable_upgrading_of_customers()
{
}
[Test]
public void Warn_when_trying_to_upgrade_customer_with_ordervalues_below_threshold()
{
}
osv
}
Jag tycker att testen blir mer självdokumenterande på detta sätt.
Sv: Feedback på DDD-delen i applikatione
TDD är alltdå inte TDD bara för att man har enhetstester eller bygger testerna först, TDD är en utvecklinsmetodik typ där man har en del viktiga moment att följa.
Ex.
1... Skriv test på nått som inte finns (ej kunna kompilera)
2... Skriv koden man testar som inte finns (kunna kompilera)
3... Skriv logik för metoden (kunna köra)
4... refactorera
5... testa
OSV... TDD bygger alltså upp din applikation utifrån in där man har tester på varje enhet (alla publika metoder) skall man vara ännumera korrekt skall man helst även mocka de underliggande objekt som kan komma fram under sin TDD approch. Dvs. Klass A kanske extraheras till att bli en Klass A och B där A använder sig av en enhet i B. Båda dessa enheter skall testas och helst skall man kunna mocka bort B för att bara testa A och inte vara beroende av det B gör osv...
Mvh JohanSv:Feedback på DDD-delen i applikatione
1... Skriv test på nått som inte finns (ej kunna kompilera)
2... Skriv koden man testar som inte finns (kunna kompilera)
2.1 Se till att testet "failar" - Detta är viktig för att veta att man kodat sitt test så att det inte alltid lyckas
(man brukar tala om red/green/refactor)
3... Skriv logik för metoden (kunna köra)
4... refactorera
5... testa
mao
1. Skriv testet
2. Få det att kompilera
3. Se till att det är rött
4. Koda logik (så enkelt som det går) så att testet blir grönt
5. Refactorera om det behövsSv: Feedback på DDD-delen i applikatione
En annan sak många glömmer är även de negativa testerna som är minst lika viktiga och nästan inte viktigare. Dvs testa felen så även de blir gröna.
Idag kan ju ens klass kanske kasta typ 2-4 olika exceptiontyper även dessa bör testas så man ser att alla verkligen går genom. Samma med booleanska värden m.m. inte bara testa att det blir ture utan även false. Det blir många tester men kvalitén ökar markant då man även tar del av hur felen är tänkta att fungera/hanteras
Mvh JohanSv:Feedback på DDD-delen i applikatione
Tack för feedbacken! Jag ska se över strukturen.
Jag antar att du skulle mer acceptera om MyMemoryBLL byta namn till MyMemoryDomain. Jag är dock lite osäker på detta med DAL. Vi snuddade lite i detta i mitt förra inlägg och som jag förstod det på er så ville ni att dataaccessen görs i repositoryn. Jag gillar inte detta på något sätt. Jag skulle helst vilja ha min MyMemoryDomain så ren som möjlig och inte blanda in mockning mm där. Jag skulle nog mer vilja att mitt DAL skulle hantera mockningen. Men jag får väl lära om :)
Jag antar att den Add-metod du reagerade på var i GameResultRepository. Jag håller med om att den borde hetta något annat. Typ Save eller något liknande. Ska fixa till det.
Det jag alltså bör göra är:
1. Ta bort mitt DAL och flytta upp den logiken till min repository
2. Byt namn på MyMemoryBLL till MyMemoryDomain
3. Byt namn på Add-metoden till Save.
4. Se över namnen på mina enhetstester så att de är mer förklarande.
Är det något mer eller om jag missförstått något av det ni sagt så hör gärna av er! Jag återkommer när en ny version ligger ute!Sv: Feedback på DDD-delen i applikatione
Nej jag menar inte att det du ser som DAL skall vara i repositoryn. Jag mernar att repositoryn använder sig av data access klasser eller andra klasser mot datakällor.
Dvs de aggerar då bara som hjälpklasser men resten finns i repositoryn.
Ex:
Nytja ADO .Net i Repository = ADO .Net är Data access klasserna
Nyttja ORM mappers i repository = ORM är data accessen som i sin tur använder sig av ADO .Net
Nyttja XML klasserna i repositoryn = XML dataaccess
Det du gör i dina DAL är att du har logik för dina domän problem i dem typ Selekterringar m.m. sånt som jag skulle lagt i mina repositories. Sen kan man ha sådana klasser som du har men DAL ordert blir lite fel då det inte direkt fungerar så som man tänkte förr med PL, BLL och DAL.
BLL och DAL ser jag egentligen mer som modullager än lager lager typ...
Så i DDD finns inget DAL eller BLL glöm det... Domänlagret med sina beståndskomponenter är typ BLL och DAL i ett fast själklart behöver inte domänlagret vara ett enda projekt utan flera men de ingår ändå i domänlagret.
Det är inte namnet Add som jag anser är fel utan vad du gör med den. varför bool? för att säga det gick rätt eller fel? vart tog exception vägen då? och den detaljerade informationen?
Din save behöver inte säga: Jag gick bra, det gör den om inte ett exception kastas typ.
Det var lite det jag menade, hade bara inte tid o beskriva det då :/
Det är skitsvårt att i text här på pellesoft forum säga vad DDD är. :-(
Men hoppas det klarnar lite... Bäst är om du läser Evans DDD bok om du inte redan gjort det?
Mvh JohanSv: Feedback på DDD-delen i applikatione
Glömde att tipsa om MVC Storefront som är ett gäng med webcasts där Rob Conery bygger en webshop mha TDD, DDD och Asp.net MVC.
http://blog.wekeroad.com/mvc-storefront/mvc-storefront-part-1/Sv:Feedback på DDD-delen i applikatione
Han pratar inte om DDD, han kan inte TDD men försöker.. Rob har svårt med OOP och han är från grunden en kille som genererar kod från databas och bara jobbat med enkla funktioner och inte object orientering. Det är många DDD "experter" som har skickat arga comment till Rob.. hans Repository är inte Repository utan en Query Provider, hans Service sätter ihop domän modellen och det är Repositorys uppgift att göra det.. Så han gör något helt eget, som kan gillas eller ogillas.. jag gillar det inte, vissa saker är intresanta så som användning av IQueryable, men det är fortfarande en fråga han skickar vidare som vid senare skeder utförs och defered execution kan ställa till med problem. Att använda hans "experiment" (som det är) som en guidlines eller riklinje för att skapa apps idag, ska man nog vänta lite med..
Ett tips, köp Jimmy Nilssons bok om DDD och även Eric Evans..
Mvh Fredrik Normén
ASP.Net MVP MEET ASPInsider
http://weblogs.asp.net/fredriknormenSv: Feedback på DDD-delen i applikatione
Jag måste säga att jag gillar iden med IQueryable, framför allt att hela iden som han använder sig av minskar mängden metoder i din repository där man kommer ha massor med överlagrade metoder för att hämta ut data baserat på olika parameters.
Nackdelen är väl dock istället att man ju lägger sådan logik inne i sin kod istället för i sina repository, och nackdelen blir ju att datakällan måste ha stöd för LINQ. Det är ju ganska svårt att ställa en fråga till en webservices via hans IQueryable listor och bara hämta ut den information som han har filtrerat ut. Hans lösning måste vara att hämta alla information från webservicen och lägga det i en IList och därefter använda sina filter...
Iden är god och tankvärd (finns det något sådant ord???), men helt plötsligt har man knutit ihop sin datakälla väldigt mycket hårdare mot sin modell än önskvärt...
- MSv:Feedback på DDD-delen i applikatione
Vilket bryter mot flera grundläggande DDD principer. Hans jakt på att kunna komponera frågor så dynamiskt som möjligt driver hans design tillbaka till client/database tänket från 80-talet. Ideén med en repository är att du skall abstrahera bort frågespråk och persistence medium och få ett snyggt use-case baserat api som är lätt att använda och underhålla.
Om du skickar upp och runt IQueryable så sprider du ut frågeskapandet och tappar kontrollen över var och när frågorna skapas. Då kan du nästan lika gärna bygga T-SQL i gränssnittet.Sv: Feedback på DDD-delen i applikatione
Jag vet och det är problemet, men det är en häftig lösning med IQueryable objektet framför allt eftersom LINQ TO SQL bakar ihop alla dessa filter's som man lägger på efter hand och gör en "optimerad" SQL-sträng mot databasen. Så jag gillar ideen, men den har lite förstora nackdelar för att man skall kunna implementera det på ett bra sätt.
För är det något som jag inte gillar med repository delen så är det alla specifika metoder som måste skapas för att få fram rätt delmängd data från datakällan, eller några få stora generella metoder (som lätt bli ostrukturerade) för att dynamiskt hämta rätt delmängd. Men livet är inte perfekt....
- MSv:Feedback på DDD-delen i applikatione
"För är det något som jag inte gillar med repository delen så är det alla specifika metoder som måste skapas för att få fram rätt delmängd data från datakällan, eller några få stora generella metoder (som lätt bli ostrukturerade) för att dynamiskt hämta rätt delmängd. Men livet är inte perfekt.... "
Ta en titt på Specifcation pattern, bara en tanke ;)
Rob flyttar problemet till sina "Services", dessa har massa metoder istället. Så han flyttar bara på det ett steg längre upp. Så han måste ändå implementera masas metoder i sina Service klasser, det värsta är att han låter sin Service bygga ihop modellen istället för att hans Repository gör det.
Mvh Fredrik Normén
MVP - MEET - ASPInsiders
http://weblogs.asp.net/fredriknormenSv: Feedback på DDD-delen i applikatione
Nja, inte det jag tänker på. I och med att hans collections är av IQuyerable så kan han ju använda LINQ till att ställa frågor mot sin collection som sedan bakas in SQLsatsen senare. Visst så skapar han några extensions metoder där han lägger denna LINQ kod. Och visst så flyttar man logik från repositorna upp något lager, och det är inte bra. Men rent generellt hade man kunnat göra så här i sin servicen:
<code>
IList<Customer> customers = from customer in repository.GetCustomers()
where cusomer.Name == "Kalle";
</code>
istället för
<code>
IList<Customer> customers = repsository.GetCustomerByName("Kalle");
</code>
Så ser jag inget speciellt fel idet, men återigen problemen är att man inte lika enkelt kan byta ut sina datakällor, eftersom datakällan måste stödja LINQ för att fungera. Men det hade varit så skönt att slippa en massa metoder i repositorina för att hämta ut data med olika parameters. Speciellt om man har olika sökkriterier som kan/skall fyllas i och sedan skall man hämta data som matchar de inmatade värden.
Men jag får väl till på Specification pattern och se om det kan minska ner mina metoder...
- MSv:Feedback på DDD-delen i applikatione
Om du använder dig av IQueryable så vet du inte exakt när och vart frågan ställs mot tex databasen, vad tror du då händer om någon hinner förändra den underliggande datan? I det fall kan du få felaktig data, data du inte förväntat dig. Tex för den aktuella tidpunkten då du begär Customers genom customerRepository.GetCustomers() och returnerar IQueryable, då får du inte ut de kunder som exakt vid den tidpunk existerar. Skulle då någon ändra, lägga till, ta bort kunder innan du hinner köra din LINQ fråga, så kan det leda till att du får oväntad data.
LINQ är en ny spelare som dykt upp. Så det finns intresanta saker man kan göra, saker som kan lösa problem och skapa nya patterns.
Mvh Fredrik Normén
MVP - MEET - ASPInsiders
http://weblogs.asp.net/fredriknormenSv:Feedback på DDD-delen i applikatione
Hela idén med repositories i DDD är att dess metoder skall hantera dina where dessa skall inte läggas i högre lager. Så istället exemplet är mer korrekt där man i ditt exempel med where hade haft som kod i respoistorien istället fast då inte anropa repositoryn o from:en utan ev direkt mot dess datacontext.
typ:
<code>
public IList<Customer> GetCustomersByName(string name)
{
...
IList<Customer> customers = (from customer in customerDataContext
where cusomer.Name == name).ToList();
return customers;
</code>
Där just detta hör hemma....
Mvh JohanSv: Feedback på DDD-delen i applikatione
Om jag inte tycker att den code smellen är viktig eller jag inte anser att fördelarna med SRP (SoC) är så viktiga för min lösning, då funkar Robs lösning alldeles utmärkt. Men Jag anser nog att de underhållsfördelar och möjligheter till förändring som det ger att följa de principerna gör det absolut värt att hålla sig borta från Shotgun surgery.Sv: Feedback på DDD-delen i applikatione
"Om du använder dig av IQueryable så vet du inte exakt när och vart frågan ställs mot tex databasen,"
Jodå, det vet jag, det kanske inte syns tydligt i koden, men i mitt exempel ovan så körs den först när jag itererar över collectionen, men för att göra det tydligare så kan man ju göra en .ToList() samtidigt som som man kör koden så vet du när den körs (då hade ju koden fungerar också :-) ).
"Skulle då någon ändra, lägga till, ta bort kunder innan du hinner köra din LINQ fråga, så kan det leda till att du får oväntad data."
Det beror på hur du ser det? Antingen så har du korrekt data när du hämtar den och felaktig när du visar den, eller så har du "felaktig när du hämtar den" och korrekt när du visar den. Så ibåda fallen så har jag problem, problem som man måste ta höjd för när man utvecklar...
@Johan, @Patrik
"Hela idén med repositories i DDD är att dess metoder skall hantera dina where dessa skall inte läggas i högre lager. Så istället exemplet är mer korrekt där man i ditt exempel med where hade haft som kod i respoistorien istället fast då inte anropa repositoryn o from:en utan ev direkt mot dess datacontext. "
Jag vet, men det är så otroligt irriterande att skriva 17 olika metoder för att hämta data på 17 olika sätt, det blir bara en massa anrop ner genom en massa lager. Det hade varit så himla smidigt att bara skriva som jag skrivit... men tyvärr är ju nackdelarn lite för många...
jag får väl titta på Specification Pattern som Fredrik nämde och se om det hjälper till...
- MSv:Feedback på DDD-delen i applikatione
Fast du måste ändå skriva kod 17 ggr för hämta på 17 olika sätt... :-) Fördelen är att du nu har ett ställe där du gör det än utspritt i koden på massa olika ställen.
Mvh JohanSv: Feedback på DDD-delen i applikatione
Ja men så mycket mindre kod jag måste skriva :-). Och dessutom så är det inte mer utspritt så länge det ligger in doman-servicen, Den ligger ju bara ett steg längre upp i lagret. Men visst som patrik påpekade så bryter det ju mot SoC så frågan är hur "pragmatisk" ;-) man vill vara...
- MSv:Feedback på DDD-delen i applikatione
Varför blir det mindre kod? eller ja du slipper ju ev metodens deklaration o din return ;-)
Sen är det inte så att ju mindre kod man har ju bättre är det, det man får se är vad man tjänar på sin kod. En metod kan kännas onödig men vinsten att enkelt kunna ändra den och veta vart den är är mer guld värd än de extra rader den kan kräva....
Sen är det ju oxå skillnad på SoC rörande LINQ to Objects och LINQ to SQL i denna fråga, Har du ex dina objekt i lista i servicelagret så ser jag inge problem att du kör LINQ to Objects i dem...
/JohanSv:Feedback på DDD-delen i applikatione
Ritesh Rao har en rätt skön post om hur man kan kombinera Specification med Repository som du kan kolla på:
http://www.codeinsanity.com/2008/08/repository-pattern.htmlSv:Feedback på DDD-delen i applikatione
["Om du använder dig av IQueryable så vet du inte exakt när och vart frågan ställs mot tex databasen,"
Jodå, det vet jag, det kanske inte syns tydligt i koden, men i mitt exempel ovan så körs den först när jag itererar över collectionen, men för att göra det tydligare så kan man ju göra en .ToList() samtidigt som som man kör koden så vet du när den körs (då hade ju koden fungerar också ). "]
Om du utför frågan i dina Repositories så har du centralicerat och vet precis när frågan utförs, tex om du anropar customerRepository.GetCustomers() så vet du att frågan har utförts, men om du skickar vidare en IQueryable så flyttar du på utförandet av frågan och på så sätt tappar "kontrollen", frågan kan utföras i en Service klass, men om någon annan använder din Repositroy så kanske den utförs först i presentations lagret.
["Skulle då någon ändra, lägga till, ta bort kunder innan du hinner köra din LINQ fråga, så kan det leda till att du får oväntad data."
Det beror på hur du ser det? Antingen så har du korrekt data när du hämtar den och felaktig när du visar den, eller så har du "felaktig när du hämtar den" och korrekt när du visar den. Så ibåda fallen så har jag problem, problem som man måste ta höjd för när man utvecklar... "]
Självklart, men om du laddar något helt vanlig och bygger en fråga som baserar sig på den första datan du laddade. Så körs kanske frågan efter en stund.. tex du kanske har massa annan logik som sker innan du ber frågan exekvera. På den tiden kan datan hunnit ändrats i databasen så att det du laddade först inte längre stämmer med det du laddade i din deferred execution.
Som Patrik nämner så får du en spridning på dina frågor, låt oss säga att du vill hämta ut en kund med hjälp av ett Kund id, i ditt exempel skriver du typ:
from customer in customerRepository.GetCustomers()
where customer.CustomerID == ID
select customer
Detta skriver du tex i din Service klass eller kanske tom i presentationslagret när du tex trycker på en knapp för att hämta en kund.. vilket som, tänk nu att du har flera ställen i din kod som behöver hämta en kund med hjälp av ID, då kommer du repetera dig själv.. om du då skapar en GetCustomerByID method och lägger denna fråga, så kommer du kunna återanvända GetCustomerByID istället för att ha frågan på flera ställen. Skulle du behöve modifiera frågan, så har du ett ställe att ändra än flera. Dessa typer av metoder placerar du dina Repositories. Så hur du än gör så kommer du inte undan dina metoder.
Mvh Fredrik Normén
MVP - MEET - ASPInsiders
http://weblogs.asp.net/fredriknormenSv: Feedback på DDD-delen i applikatione
Om man då använder sig av en ORMapper och den ha ett frågespråk så kan man ju använda sig av det som inputparameters till repositoryt, men då har man ju knutit sig hårtfast till den ORMappern i sin applikation :(
Men man kanske skulle kunna använda LINQ som inputparameters och sedan bygga ihop en LINQ fråga mot sin datakälla. Även om inte LINQ är implementerat överallt så är det ju ändå så att fler och fler ORMappers kommer implementerar LINQ och vill man gå direkt på databaskällan så kan man ju använa LINQTOSQL.
Man skulle alltså kunna använda Specifications patterns där LINQ får vara "frågespråket". Så får man det dynamsikt och samtidigt någorlunda löst kopplat....
- M