25. januar 2005 - 12:32Der er
73 kommentarer og 3 løsninger
ObjektModel (og Patterns) i ASP.NET
Hej Alle..
Jeg skal til at lave en lidt større ASP.NET applikation, og er blevet lidt træt af den "typiske" webmetode, der ligger op af transactionscripting, og vil nu gerne igang med at bruge noget mere lagdelt arkitektur.
Jeg vil høre om nogen har erfaringer med at bruge andre mønstre i deres web-udvikling? Måske ikke direkte en stor forkromet domain-model, men måske andre gode? (Jeg har en rimelig god teoretisk viden omkring patterns, bl.a. en del af GoF´s og Fowlers) - så håber vi kan tage en god diskussion her.
Glæder mig til at høre hvad I eksperter derude har at sige.
Ok.. måske min forklaring er lidt henne i hegnet..
Altså.. indtil videre har jeg når jeg har lavet ASP.NET applikationer gjort således, at hver gang jeg har skulle vise noget data, så har jeg igennem en DB-facade hentet en datatable, som jeg så har vist i GUI enten via databindning eller ved selv at udskrive det så det passede. Når jeg så har skulle opdatere har jeg genereret nogle UPDATE eller INSERT SQL-statements, som jeg så har fået eksekveret igennem min DB-facade.
Nu har jeg så de sidste par dage gået med tanken om at lave det sådan lidt mere som jeg kender fra "alm. applikationsudvikling" - hvor jeg tænkte på at lave nogle domæneklasser. F.eks. En "Bruger","Nyhed" eller en "Kommentar" klasse, som jeg så ville lave en Mapper klasse til, således at ved en opdatering kunne jeg kalde min mapper, og den ville sørge for at opdatering eller indsættelsen hvis det var et nyt objekt.
Det er sådan set det jeg gerne vil have noget feedback på, eller en lille snak ok! :-)
det lyder da som en simpel O/R-mapper ? Well, ikke fordi at sådan en er simpel at lave, men er meget simpel at bruge. Der findes forskellige O/R-mappere rundt om på nettet, arne har vist sin favorit, kan ikke lige huske hvad den hedder.
Selv har jeg udviklet en mere specifik-løsning hvor jeg har et IDbObject-inteface som jeg kan lade mit object implementere hvis at det skal kunne gemmes i en database/xml-fil. Dette interface indeholder nogle metoder som svarer til Update, Insert, Select og Delete, og de bliver automatisk kaldt af det underliggende system når at der er sket ændringer i objectet. Altså ikke noget man som udvikler af selve UI/præsentations-laget skal tænke på.
Jeg skal så have valgt et passende "Domain Logic Pattern" - hvis vi lige skal tage lidt udgangspunkt i Fowler, men er det muligt (og evt. klogt?) at lave f.eks. en Domain Model i en webapplikation?
Ved ikke hvorfor jeg altid har set det som værende "forskellige" ting. Dvs. som en "in-memory" kopi af min logik, så ville man bare loade det ind i en Application-Variabel ?
så mange du har brug for... og hvis det kniber med hukommelsen, så smid nogle flere ram i maskinen :P
Jeg har personligt været ude for under udviklingen af en webshop, og jeg endte med at loade alle produkterne op i databasen for at få perfomancen til at være acceptabel. Problemet lå i at hver produkt bestod af så mange underkomponenter at det tog for lang tid at generere et object af produktet hver gang det skulle vises.
Jeg synes ikke man skal være for nærig med sådan noget. Har man brug for at gemme noget i hukommelsen, så gør det sq. Sådan noget som Cache er da også Gates's gave til os udviklere, så at lade være med at bruge det for "ååh, det bruger for meget hukommelse"... det er noget crap.
Hvor meget fylder ens data. Jeg er helt enig med cf i at til normale ting så smider man bare 512 MB mere i maskinen - det koster ikke meget. men måler man ens data i TB, så bliver, man nødt til at revurdere ens cache strategi.
Hvad er sandsyneligheden for at data skal bruges igen snart. Eller hvad bliver cache hit rate. Selvfølgelig kun relevant hvis man ikke gemmer alt.
Krav til data integritet og valget mellem writeback og writethrough.
Thona consulting har en rigtig god forklaring om hvorfor MS/VS.NET stadard måden er skod og hvorfor ORMapper bare er sagen Desvære skal man være oprettet som bruger (gratis) for at downloade PDF filen http://www.thona-consulting.com/content/forms/download.aspx
Session, Cache og Application er de eneste tre muligheder du har for at gemme noget du kan få fat i igen. Ved ikke helt hvad du lægger i ordet "Application-variabel", men ja... hvis du skal gemme noget alle skal have fat i skal det i applikation. Ting der kun er gældende for den enkelte session skal i.. ja, session. Cache er lidt ligesom Application, men har nogle najs features som timeout, events og filovervågning.
Det burde måske nævnes at en ORMapper sørger for caching. Og der er lazy-loading på relationer altså: Bruger br = ORmapper.Get(TypeOf(Bruger), id); string navn = br.Navn; // navn er hentet SpoergsmaalListe liste = br.Sporgsmaal; // Lazy-loading, først nu hentes alle spørgsmål ud som tilhører brugeren
Documentation fra thona-consulting, får man vist kun hvis man downloader produktet fra dem... hvis du smider en e-mail skal jeg sende PDF'en (800KB) til dig
Noget af det som jeg syntes kan være lidt skod, når man udvikler sit eget system er sådan noget som: ProfilSet profiler = bll.HentProfilerSomErIndenForMinOgMaksSletningdatoOgAktive("2005.01.01", "2005.02.02", false); hvor at bll'en så kalder videre til min dal, hvor den så måske er lidt mere generisk. Her er det rart med en ORMapper hvor man kan angive sine kritier på forskellige måder, object måden er OPath som du kan google på (wilsons understøtter vist ikke OPath)
Der er ingen tvilv om at Object Spaces kommer til at dominere markedet, men desvære er det udskudt gang på gang... det sidste var vist at det først ville komme med deres nye filsystem
Wilson har bygget sin OR-mapper op omkring Object Spaces modsat mange af de andre
Jeg kan ikke sende til dig: Diagnostic-Code: smtp;552 5.2.2 This message is larger than the current system limit or the recipient's mailbox is full. Create a shorter message body or remove attachments and try sending it again.
Og en af de lidt trælse ting ved O/R mapping tools er et stort set hver eneste af dem har sit eget query language. Og ja de ligner hinanden meget, men er netop ikke identiske.
Nu har jeg kigget lidt videre på det, og har besluttet mig for, at mit nye projekt skal bruge en fuld "domain model", men vil gerne have planlagt hele min applikation inden jeg starter på den! (Hvilket nok er normal god praksis *g*)
Mit næste problem er så at jeg skal vælge et "Data Source" mønster. Jeg har kig på "Data Mapper", da jeg ved at jeg vil få n <-> n forbindelser imellem nogle af mine objekter, som vel så vil ende som en "Association Table Mapping", og det er mit indtryk af f.eks. "Table Data Gateway" og "Row Data Gateway" har som udgangspunkt at objekterne i modellen afspejler netop 1 tabel/row ?? Vil nogen kommentere på dette? og er det evt. nogen der har en implementation liggende til .NET, som man kunne bruge som inspiration?
I forbindelse med dette, så tænker jeg også på hvordan jeg skal holde min Domain Model konsistent med min database. (Hvis f.eks. systemet crasher) Vil gerne have nogle foreslag her.. ?? Tænker det er lidt vildt, hvis man skal sørge for at køre en update sql igennem sin mapper hver gang en property på et objekt bliver ændret?
Glæder mig til at få lidt hjælp/kommentar/inspiration...
konsistens er jo altid et problem... hvis du ikke vil gemme i realtime (så snart noget bliver ændret) må du lave en form for autosave. Når du sidder og skriver i word er der jo også risiko for at noget går tabt da den jo heller ikke gemme så snart du taster.
En anden mulighed er at implementere end CommitChanges-metode til dine Db-objecter så du kan gemme deres state efter en større ændring.
Din database er vel en del af implementerinten af din domain model. Men du mener sikkert konsistens mellem in memory data strukturer og databasen. Hvis din software kun skal køre på single node, så er det ret nemt at opnå med writethrough. Hvis det er multinode, så er det meget sværere.
jeg har selv implementeret en CommitChanges() funktion der skriver ændringerne tilbage til min datastore igen. Denne kaldes der hvor at objectet er kaldet og der er lavet ændringer. Ved hjælp af durty-flag kan jeg så se hvilke data der er ændret siden sidst.
Eksempel:
User u = User.GetById(45); u.LastLoggedIn = DateTime.Now; u.CommitChanges();
Ang. de andre mønstre må jeg tilslutte mig arne og erkænde at jeg ikke umiddelbart har hørt om dem før. Jeg bruger ikke selv en O/R-mapper, forestået som et tredieparts produkt. For jo, selvfølgelig mapper jeg mellem mine objecter og databasen, men det sker igennem kode jeg selv skriver/genererer.
Cyberfessor: ok, tak for det. Har et sidste spørgsmål, som I måske vil hjælpe med.
Jeg har i min applikation en opbygning, som kommer til at hedde en "User" klasse og en "Group" klasse. En User kan være tilknyttet mange grupper, og dermed også omvendt. Jeg har brug for at kunne gå begge veje, men ikke nok med det, så har hver User også forskellige rettigheder i de forskellige groups. Hvordan ville I modellere dette som Objekter og som nedenunder liggende database ?
Da jeg er bedst til at tænke hvordan det skal opbevares i dben, så tænker jeg her tabellerne: User <-> Userrights <-> Groups
- men, hvordan ville I gøre det objekt mæssigt?
Ved godt det er et sidespring, og kan evt. oprette et andet spørgsmål?
Jeg bruger selv Table Data Gateway for at kunne mappe mine objecter ned i database, og op igen, uden at smaske selve mit object til med sql-kode. Association Table Mapping er også meget anvendt. Dog har jeg aldrig brugt noget der ligner Row Data Gateway, og har svært ved at se dens styrke i andet end NUnit-testning.
jeg ville nok lave en slags facade hvori at man kan query på hvilke rettigheder en bruger har i en gruppe og ud af det få et Userrights-object retur.
User u = User.GetByName("Jakob") Group g = u.Groups["Admins"];
UserRights right = UserRights.Query(u, g); Response.Write(u.ToString() +" is "+ right.ToString() +" in the +" g.ToString()); // Jakob is admin in the Admins group
Group g = Groups.GetByName("Admins"); User u = g.Users["Jakob"];
UserRights right = UserRights.Query(u, g); Response.Write(u.ToString() +" is "+ right.ToString() +" in the +" g.ToString()); // Jakob is admin in the Admins group
Jeg ville gøre det enten ved at oprette en UserSet og GroupSet klasse De ville så bare indeholde et ArrayList som har objecterne i sig.
Din User klasse vil så indeholde public GroupSet Groups { get { return this.groups; } set { set this.groups; } }
Typisk ville man kalde db relation tabellen UsersInGroups
Man kunne også lave en mere general object liste
Wilson har en rimlig god en... nedenståender er taget via Reflector som giver et lille overblik public class ObjectList : ILoadOnDemand, IObjectSet, IObjectPage, IList, ICollection, IEnumerable { // Methods internal ObjectList(Context context, Type objectType); internal ObjectList(Context context, ObjectQuery objectQuery); internal ObjectList(Context context, ObjectSet objectSet); internal ObjectList(Context context, SelectProcedure selectSP); public int Add(object entityObject); public void Add(object objectKey, object entityObject); public void Clear(); public bool Contains(object entityObject); public void CopyTo(Array array, int index); public IEnumerator GetEnumerator(); public object GetObject(object objectKey); public int IndexOf(object entityObject); public void Insert(int index, object entityObject); public void Remove(object entityObject); public void RemoveAt(int index); public void RemoveByKey(object objectKey); public void Resync(); public override string ToString();
// Properties public int Count { get; } private IList IList { get; } public bool IsFixedSize { get; } public bool IsLoaded { get; } public bool IsReadOnly { get; } public bool IsSynchronized { get; } public object this[int index] { get; set; } private ObjectSet List { get; } public Type ObjectType { get; } public int PageCount { get; } public int PageIndex { get; } internal ArrayList Removed { get; } public object SyncRoot { get; }
arne_v: Ja, det er jo netop mit problem. Jeg kan godt se at en rettighedsklasse ikke ville høre hjemme i modellering af logikken, men hvordan ville du så gøre det? Jeg kan ikke se hvordan man på en bruger, så ville gemme hans rettigheder til de forskellige grupper?
"så har hver User også forskellige rettigheder i de forskellige groups"
ud fra dette vil tabellen have flere end 2 felter, da der også skal kunne beskrives hvilken rettighed brugeren har i den pågældende gruppe og det vil heller ikke være nok at have
1) en liste over grupper brugeren er med i i User-objectet 2) en liste over brugere der er med i gruppen i Group-objectet
Der mangler den sidste information, der beskriver brugerens rolle i den pågældende gruppe, og der vil jeg mene at mit forslag opfylder det. Det implementer netop arne'ss forslag med at både User og Group har en liste over Groups og Users der hører til dem PLUS at man har mulighed for at få oplyst hvilken rolle brugeren har i gruppen.
Overvejer at lave en Collection på User, som bare indeholde alle de grupper han er med i .. og så lave 3 collections på Group klassen.. en AdminCollection, ModeratorCollection og UserCollection ...
Jeg tror at det er hvad Arne_v fisker efter? Ville dette ikke dække det?
Jamen.. måske mit OO er blevet noget rustent, men jeg forstår ikke helt den løsning der arne_v!? Hvis gruppen er opdelt på 3 instanser, hvordan vil du så f.eks. hente en komplet liste over alle Users tilknyttet gruppen?
Tror jeg skal have noget skåret ud i en stykke bølgepap her?! :-)
Group admins = Group.GetUsersByLevel(UserLevel.Admin); Group moderator = Group.GetUsersByLevel(UserLevel.Moderator); Group users = Group.GetUsersByLevel(UserLevel.Users);
Group allUsers = Group.GetUsersByLevel(UserLevel.All);
christian>> men som jeg har forstået det, som ligger der stadigvæk ikke noget rettighedslag i det
Ja, du har en gruppe der hedder admin, og du kan få fat i alle brugerne i den gruppe. Men hvordan vil du kunne se hvilken rettighed en enkelt bruger har.
Jeg ville heller ikke putte funktionalitet i Group klassen... så det skulle nok nærmere se sådan ud: project.components.Group admin = project.bll.Group.GetByName("Admin"); project.components.Users users = admin.Users;
Via overstående kan du løber users igennem for at se hvem der er med i Admin gruppen, det var det som du spurgte om ikke?
Angående hvordan man tjekker om en bruger er med i en bestemt gruppe, ville jeg nok bruge det indbyggede i asp.net... det vil sige jeg ville lave BasePage som sørgede for at lægge den aktive brugeres grupper ind i den indbyggede asp.net Role modul. Og den har vist nogle indbyggede IsMemberOf("Admin")
arne_v>> ang. 27/01-2005 13:55:28 så er GetByName en statisk metode på Group-klassen, for at kunne instantiere et Group-object ud fra data i databasen. I en O/R-mapper ville det måske være noget i den her retning:
Group admin = (Group)ORmapper.InstantiateNewObject(typeof(Group), "Admin"));
men det er heller ikke den konkrete implementering der er den vigtige her. Mere hvilke typer man har og hvordan deres relation til hinanden er.
Vi kan vel alle blive enige om at der skal mindst 4 klasser til
1) Group 2) GroupCollection 3) User 4) UserCollection
Group indeholder et field til en UserCollection, og User indeholder et field til en GroupCollection.
Fint nok... men hvor skal en brugers rettigheder i en given gruppe gemmes?
Jeg er selv kommet frem til følgende: 1. Users (Indeholder en collection med alle User-objekterne) 2. User (Indeholder en collection af Group) 3. Groups (Indehollder en collection med alle Group-objekterne) 4. Group (Indehollder 3 collections til at gemme sine brugere på. Hvis en bruger er admin, bliver den bruger gemt på en AdminCollection osv.)
Jeg ser ikke nogen situationer, som den opbygning ikke kan løse?
Ok, måske jeg har fucket op i forklaringen også? Måske rettigheder ikke skal forståes som sådan, men mere en status på den enkelte User i hver group.
F.eks. at en user kan være Admin, Moderator eller User. Hvad de enkelte status´er så giver rettigheder til, det har jeg slet ikke haft inde i mine overvejelser endnu, men bare jeg havde muligheden for at se forskel på dem, sådan at jeg ved nogle funktioner kunne kalde en metode som f.eks.
dim g as new Group("Eksperten-Spørgsmål-584519") dim u1 as new User("arne_v") g.AddAdmin(u1)
:) jeg har nok taget hans forklaring lidt for bogstaveligt... :) ja, jeg så kun group som en grupperinger af nogle personer... f.eks. afdelinger i et firma. Og inde i denne gruppe skal rettighederne deles op.
Men det er da rigtigt, at hvis systemet skal være ligesom Windows's ACL så vil følgende kunne bruges, som også er den model arne_v har foreslået
Følgende fire klasser:
1) Group 2) GroupCollection 3) User 4) UserCollection
Group indeholder et field til en UserCollection, og User indeholder et field til en GroupCollection.
dog vil jeg passe på med at hardcode grupperne i koden... så til overstående vil jeg istedet gøre sådan her:
User u = User.GetByName("arne_v");
if (!u.Groups["Admin"] == null) { // Do something } else { throw new Exception("User not allowed to edit this page"); }
Men det bliver vel et spørgsmål om præferencer
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.