webForumDet fria alternativet

OR Mapperns plats i arkitekturen

.NET

27 svar · 1 053 visningar · startad av PDahlen

Medlem sedan apr. 2004778 inlägg
Frågan#1

Imorse när jag vaknade så började jag fundera på en sak (japp många idéer när jag vaknar).

Som jag ser det så ersätter en O/R Mapper Persistence lagret och DAL i arkitekturen. Den mappar BusinessObjects (entiteter och collections) mot databasen.
Om vi då behöver skapa ett BO i vår PresentationsLogik så verkar många exempel gå ut på att man använder sig av OR Mappern direkt i PL för att skapa sina BO (entiteter och collections).
Men, lämnar man inte klassiskt OO- och N-tier-tänk då? För mig så känns det som att presentationslagret anropar DAL direkt, vilket det inte borde göra. Istället borde det ligga ett BusinessLogic lager som fyller BO och levererar dessa till presentationslagret.
Dvs. i presentationslogiken så skapar vi en instans av ett BusinessObject, för att fylla det anropar vi då vår BusinessLogic, anger vilken typ av object vi arbetar med. Businesslogiken i sin tur startar upp OR Mappern som returnerar rätt objekt.

Eller är det jag som tänker för mycket i nattmössan och det är så att OR Mappern går in även i BusinessLogic lagret?

Medlem sedan juli 20011 304 inlägg
#2

Näe det är lite "fel" tänkt.

Presentationslagret kan mycket väl ropa på o/r mappern direkt om det inte finns någon affärslogik, men då ropar man på o/r mapperns object factory som i sin tur pratar med DAL. Om man pratar direkt med DAL via o/r mappern tycker jag att den är felkonstruerad.

Men i många fall har man ju ett BL som man ropar på och som i sin tur kommunicerar med o/r mappern som pratar med sitt DAL.

Sen kan man ju diskutera var man vill ha sina BO, och vissa vill ha dem i sitt dal. Jag föredrar att ha dem i ett eget namespace.

Medlem sedan dec. 19996 522 inlägg
#3

BO, det är samma sak som modell i MVC eller har jag hajjat något fel. Engelska förkortningar hit och dit ;) Buis.Objects exempelvis klassen faktura (som ett exempel).

Medlem sedan apr. 2004778 inlägg
#4

Nja, nu tror jag det handlar om lite olika definitioner.
Här är mina:

DL - DataLayer
Databasen

DAL - DataAccessLayer
Klasser som anropar databasen antingen med eller utan Stored Procedures. Detta är vad OR Mappern gör så man slipper bygga ett eget DAL.

BL - BusinessLayer
Här finns BusinessObjects (entiteter och collections) samt BusinessLogic som t.ex. fyller object.

PL - PresentationLayer
Består av PresentationsLogic och själva presentationssidorna.

Presentationslagret kan mycket väl ropa på o/r mappern direkt om det inte finns någon affärslogik, men då ropar man på o/r mapperns object factory som i sin tur pratar med DAL. Om man pratar direkt med DAL via o/r mappern tycker jag att den är felkonstruerad.

Då skulle det betyda att O/R Mapperns object factory ÄR BusinessLogic. Från Presentationslagret anropar man o/r mapperns factory (BusinessLogic) som i sin tur anropar o/r mapperns DAL som i sin tur anropar Databasen. Då följer vi n-tier och OO och svara på en av mina frågor, nämligen att o/r mappern även går in i affärslogiken.

Men i många fall har man ju ett BL som man ropar på och som i sin tur kommunicerar med o/r mappern som pratar med sitt DAL.

Om man då har egen BusinessLogic som i sin tur anropar o/r mapperns object factory så står man där antingen med dubbel affärslogik eller så flyttas object factory ner i DataAccessLayer.

Sen kan man ju diskutera var man vill ha sina BO, och vissa vill ha dem i sitt dal. Jag föredrar att ha dem i ett eget namespace.

Som du ser i mina definitioner ovan så anser jag att BusinessObjects hör till affärslagret.

Medlem sedan juli 20011 304 inlägg
#5

Då skulle det betyda att O/R Mapperns object factory ÄR BusinessLogic

Det håller jag inte riktigt med om :)
Business logic är typ räkna ihop alla orderrader på en faktura eller om priset är ojämt, öresavrunda typ. Det som o/r mappern gör är bara hämta ett/alla objekt av en viss typ som eventuellt uppfyller ett kriterum. Den ska absolut inte på något sätt lägga sig i VAD objekten innehåller eller manipulera detta - det skall nämligen göras av - just det BL

Business är i min värld som namnet antyder affärslogik - processer som tillhandahåller manipulation av dina affärsprocesser.

En annan reflektion - bara för att ens affärslogik skulle utföras på två ställen är det fortfarande samma lager - bara lite sämre fysisk uppdelning. Lagrena är egentligen endast en logisk uppdelning och man skulle i praktiken kunna göra en femlagers struktur i en enda fil eller en tvålagers struktur i 100 filer.

Medlem sedan juli 20011 304 inlägg
#6

Som du ser i mina definitioner ovan så anser jag att BusinessObjects hör till affärslagret

Jag håller nog med - och då kallar jag dem affärsobjekt.

Ibland gillar jag dock att jobba med lite "osmartare" databärare och kallar dem då DTO, data transfer objects vilka jag inte placerar logiskt i något lager utan lägger vertikalt utanför lagermodellen så att säga

Medlem sedan okt. 2002188 inlägg
#7

Så här har jag delat upp det, jag vet inte om det är det bästa eller renaste sättet. UI'et anropar applikationslagret, som kan returnerar ett BLL objekt, men skulle lika gärna kunna returnerar en array med strängar om man vill det. (då jag pillar med griddar så är det ett dataset som jag returnerar) Ui'et anropar metoder som getClient(1); som i sin tur anropar metoder som kapslar in O/R-mappern.

http://www.webforum.nu/showthread.php?s=&postid=918475#post918475

Medlem sedan apr. 2004778 inlägg
#8

Återigen definitioner som vi inte är överens om. ;)

räkna ihop alla orderrader på en faktura eller om priset är ojämt, öresavrunda typ

För mig är detta Business Rules, affärsregler. De finns i affärslagret men var beror lite på vilken metodik du använder. Skall affärsreglerna ligga i objektet eller använder man entiteter (dvs. klasser som endast innehåller properties) så de ska ligga bland affärslogiken?
Men affärslogiken för mig innehåller också bryggan mellan presentationslagret och dataaccesslagret. Kan ta ett exempel som jag läste i nån bok, ska se om jag kommer ihåg vilken.
Databasen innehåller tabeller och stored procedures
DAL innehåller funktioner som anropar databasens sp:s och returnerar en IDataReader.
BusinessLayer innehåller BusinessObjects med properties, affärsregler och funktioner för att fylla själva objektet med den reader som DAL returnerar.
MEN, i en annan lösning om du t.ex. genererar koden för klasserna så är det inte så bra att ha affärsregler och specifika funktioner inuti objekten. Detta eftersom om du måste generera om en klass måste lägga in dessa regler på nytt. Då läggs istället "fyll"-funktionerna och affärsreglerna i BusinessLogic delen av vårt BusinessLayer.

Kanske rörigt men det är lika bra att vrida och vända på allt. :)

Medlem sedan juli 20011 304 inlägg
#9

Allt det där är ju omdebatterat och det finns som sagt flera åsikter. Det är alltid bra att diskutera och ifrågasätta allt! :)

Jag anser att problemet som du har identifierat i ursprungsfrågan beror på det synsätt du har på lagrena och dess design. I min värld existerar inte dessa problem eftersom:

0. Databasen, inga Stored procedures. Vill inte ha affärslogik i databasen och vill kunna byta ut den till vad som helst!
1. Allt prat med databasen sköts av O/R mappern via dess DAL
2. Alla affärslogik sköts av BLL
3. Mitt UI ropar på processer i BLL och visar ut detta på en hemsida eller något annat GUI.
4. Mina BO är dumma behållare av data, innehåller ingen logik

Medlem sedan apr. 2004778 inlägg
#10

Ja, att det finns flera åsikter stämmer bra det.
Däremot så anser jag inte att problemet i ursprungsfrågan beror på mitt synsätt på lager.
Det jag försöker identifiera är:
Om man ska använda en O/R Mapper, vilka delar av min arktiektur skall den ta hand om. Som jag skrev i mitt förra inlägg så finns det olika typer av arkitekturer och jag beskrev två. Detta gör att beslutet om man ska använda en mapper eller inte även inkluderar om man kan lägga in mappern i en redan befintlig arkitektur och i så fall var, eller om man ska bygga en ny arkitektur.

"Din värld" är densamma som min och i så fall borde du kunna svara på detta.
Du skriver att ditt UI anropar ditt BLL, innebär det att du på det sättet får tillbaka dina fyllda BO, vilket i sin tur innebär att du i ditt BLL har funktioner som anropar O/R Mappern som fyller objekten? ;)

Medlem sedan maj 20012 812 inlägg
#11

Tänkte bara lägga ner mina 5 ören...

Att beror faktiskt på hur man använder sin O/RMapper. Antingen så låter man O/R Mappern skapa ett Nytt object utifrån en type som man skickar med. Eller så låter man O/R Mappern fylla redan ett skapat object. Vad som är mest rätt kan vi låta vara till en annan diskution.

Om vi skapar objekt från vår O/R Mapper så har vi iprincip slagit ihop DAL och BL till ett lager där vi inte kan gör New Object() utan måste göra ORMapper.GetObject(). Alltså så låter vi vårt UI ha direkt åtgång till ORMappern eftersom den nummera är en del av vårt BL.

Om vi istället låter vår O/RMapper fylla ett redan skapat object så kan vi inkapsla O/RMappern i klass med en method typ Load() eller kanske direkt i konstrukter. Vi har då bara gömt vårt DAL för BL som nu istället känner till O/RMappern som i sin tur känner till DAL:et

På det senare sättet så låter vi vårt UI prata med BL som pratar med O/RMappern som pratar med DAL:et.

Det kräver dock att vi låter vår objekt vara smarta och kan hantera affärslogik.

Ett annat sätt är att lägga ett ProcessLager under UI som i sin tur kallar på BL eller O/RMappern (beroende på hur den är uppbygd) och då är dina object så dumma som möjligt alltså bara innehåller data och verifieringsmetoder, och ditt Process lager sköter all din affärslogik.

Men som sagt allt har med tycke och smak att göra, huvudsaken man trivs med den lösning man har så är det bra, man skall dock alltid utveckla den, så disskustioner är alltid bra.

Medlem sedan maj 20012 812 inlägg
#12

Ibland gillar jag dock att jobba med lite "osmartare" databärare och kallar dem då DTO, data transfer objects vilka jag inte placerar logiskt i något lager utan lägger vertikalt utanför lagermodellen så att säga

Jag brukar göra något liknande, jag har ett Defenitionslager som delas av nästan alla lager i min applikation, dock så har jag inte några rena objekt utan endast interface i detta lager vilket är att föredra fram object, då det blir lättare att byta ut ett lager om man använder interface istället.

Medlem sedan apr. 2004778 inlägg
#13

Hehe, har väntat på att du ska komma med dina 5 öre, eftersom jag vet att du är haj på det här. ;)

Nu klarnade faktiskt det mesta. I de scenarion jag beskrev tidigare så går det att använda en mapper i båda, man använder dem helt enkelt på olika sätt beroende på arkitekturen, om jag fattat rätt.

Personligen så klingar det bäst att låta mappern fylla ett redan skapat objekt med ett ProcessLager (skulle jag kalla affärslogik) som anropar mappern. Men vi får väl se var jag hamnar när jag väl börjar bygga in en mapper i mina projekt. Det är alltid tid som saknas.

Medlem sedan juli 20011 304 inlägg
#14

Du skriver att ditt UI anropar ditt BLL, innebär det att du på det sättet får tillbaka dina fyllda BO, vilket i sin tur innebär att du i ditt BLL har funktioner som anropar O/R Mappern som fyller objekten?

Så är det! Nu förstår jag vad du är ute efter. Bra uppdelning av Gladh oxkså.

Om jag jobbar med DTO:erna så blir det separerat tycker jag men om man jobbar med mer kompetenta BO så blir O/R mappern en del av mitt BLL precis som du skriver :)

Om vi skapar objekt från vår O/R Mapper så har vi iprincip slagit ihop DAL och BL till ett lager där vi inte kan gör New Object() utan måste göra ORMapper.GetObject(). Alltså så låter vi vårt UI ha direkt åtgång till ORMappern eftersom den nummera är en del av vårt BL.

Detta beror ju lite på hur flexibel O/R mapper man arbetar med. Man kan ju tänka sig att man antingen gör ett new objekt() och skickar till mappern som fyller på det eller låter den skapa objektet beroende på typeof()

Medlem sedan okt. 20005 273 inlägg
#15

Mitt inlägg i debatten.

Tjenare.
Då Gladh sagt åt mig ett flertal gånger att jag ska använda mig av ORMapper så har jag tagit åt mig ORMappern som Paul Wilson skapat.

Jag tänkte använda den på följande sätt i mina kommande projekt.

- DL
-- Databas

- DAL
-- Mitt DataAccessLayer som jag använder om jag vill fylla mina object med lite mer avancerade SQL-satser.
Detta lager använder jag mindre för att mer o mer gå över till ORMappern

- ORMappern
-- Min ORMapper som fyller mina object eller collectioner.

- BOL
-- Mitt BusinessObjectLayer där alla object och entiter finns.

- BLL
-- Mitt BusinessLogicLayer där jag fyller alla mina BusinessObjects Med hjälp av antingen ORMappern eller mitt DAL.

- PL
-- Mitt PresentationLayer där jag binder all min data till olika kontoller i antingen en aspx eller winform.

Där har ni min lösning. Nu till lite kod, databasen behöver man inte visa...och min DAL ser nog lite ut som alla andras.
Den returnerar dem vanliga sakerna(datareader, datasets osv osv) mer hur jag använder den senare.

Sen mitt BOL så har jag t.ex följande

[red]
using System;
using icaaq.wf.business;
using System.Configuration;
using Wilson.ORMapper;

namespace icaaq.wf.businessObjects
{
	public class TblSeller
	{
		private int pkSellerId;
		private DateTime fldDate;
		private int fldContact;

		public int PkSellerId
		{
			get { return this.pkSellerId; }
		}

		public DateTime FldDate
		{
			get { return this.fldDate; }
			set { this.fldDate = value; }
		}
		public int FldContact
		{
			get { return this.fldContact; }
			set { this.fldContact = value; }
		}
		public int Add()
		{
			ObjectSpace myORLogic = new ObjectSpace(ConfigurationSettings.AppSettings["ObjectMapping"], ConfigurationSettings.AppSettings["ConnectionString"], Provider.Access);
			TblSeller s = (TblSeller)myORLogic.GetObject(typeof(TblSeller));
			s.FldContact = this.fldContact;
			s.FldDate = this.fldDate;
			myORLogic.PersistChanges(s);
			return s.pkSellerId;
		}
	}
}
 [/red]

TblSeller har ORMappern skapat mha ORHelpern som mappar upp hela databasen och även skapar de olika objekten. Det jag har lagt till i classen TblSeller är alltså Add() funktionen (Vilken man kanske kan snygga till ytterligare) Men jag tycker att den funktionen snabbar upp kodningen i mitt PL.

Om vi sen går vidare till BL så har jag där först en konstuktor

[red]
	public class Logic
	{
		private ObjectSpace myORLogic;
		private OdbcData myDataLogic;

		public Logic(ConnectionType cType)
		{
			switch(cType)
			{
				case ConnectionType.ORMapper:
					myORLogic = new ObjectSpace(ConfigurationSettings.AppSettings["ObjectMapping"], ConfigurationSettings.AppSettings["ConnectionString"], Provider.Access);
					break;
				case ConnectionType.DataAccessLayer:
                    myDataLogic = new OdbcData(ConfigurationSettings.AppSettings["ConnectionString2"]);
					break;
			}
		}
..
..[/red]

Där jag väljer om jag ska använda mig av ORMappern eller mitt DAL. Sedan om jag använder mig av ORMappern så ser det t.ex ut så här

[red]
public ICollection GetAllProvinces()
{
	return myORLogic.GetCollection(typeof(ArrayList), typeof(TblProvince), string.Empty);
}
public ICollection GetNeighbourProvince(int SelectedProvince)
{
	QueryHelper Helper = myORLogic.QueryHelper;
	string where = Helper.GetExpression("pk_ProvinceId in (SELECT fldNeighbourProvince from tblProvinceNeighbour where fldSelectedProvince = "+SelectedProvince.ToString()+")");
	return myORLogic.GetCollection(typeof(ArrayList), typeof(TblProvince), where);
}
[/red]

Eller om jag använder mitt DAL

[red]
public ArrayList GetSearch(string SearchWord, int Category, int Province, bool Neighbour, int Size, int type)
{
	string SqlStatement = "SELECT pk_ArticleId, fldDate, fldHeadline, fldContent, fldPicture, fldProvinceName, fldcategoryName " + 
		"FROM ((tblSeller AS s "+
		"INNER JOIN tblArticle AS a ON a.fk_SellerID=s.pk_SellerId) "+
		"INNER JOIN tblProvince AS p ON p.pk_ProvinceID=s.fldProvince) "+
		"INNER JOIN tblCategories AS c ON c.pk_CategoryId=a.fk_CategoryId "+
		"WHERE (a.fldHeadline LIKE '%"+ SearchWord +"%' OR a.fldContent LIKE '%"+ SearchWord +"%') "+
		"AND a.fldSellingType = "+type.ToString()+" ";
	if(Category != -1)
	{
		SqlStatement += "AND a.fk_CategoryId = "+Category +" ";
	}
	if(Province != -1)
	{
		if(Neighbour)
		{
			SqlStatement += "AND (s.fldProvince IN (SELECT fldNeighbourProvince from tblProvinceNeighbour where fldSelectedProvince = "+Province.ToString()+") OR s.fldProvince = "+Province.ToString()+") ";
		}
		else
		{
			SqlStatement += "AND s.fldProvince = "+Province.ToString()+" ";
		}
	}
	Debug.WriteLine(SqlStatement);
	OdbcDataReader dr = myDataLogic.RetriveDataReader(SqlStatement);
	ArrayList aCollection = new ArrayList();
	while(dr.Read())
	{
		Article a = new Article();
		a.Id = Convert.ToInt32(dr["pk_ArticleId"]);
		a.FullDate = Convert.ToDateTime(dr["fldDate"]);
		a.Headline = Convert.ToString(dr["fldHeadline"]);
		a.Content = Convert.ToString(dr["fldContent"]);
		a.Province = Convert.ToString(dr["fldProvinceName"]);
		a.Category = Convert.ToString(dr["fldCategoryName"]);
		if(Convert.IsDBNull(dr["fldPicture"]))
		{
			a.Picture = false;
		}
		else
		{
			a.Picture = true;
		}
		aCollection.Add(a);
	}
	dr.Close();
	return aCollection;
}
[/red]

Nu har jag bara mitt PL kvar och där binder jag ju bara de olika collectionerna som jag hämtat från mitt Logic Layer. Samt att jag sparar datasom jag fått in genom att t.ex göra följande

[red]
		private void addSellerClick(object sender, System.EventArgs e)
		{
			TblSeller s = new TblSeller();
			s.FldProvince = Convert.ToInt16(this.province.SelectedItem.Value);
			s.FldName = this.nameBox.Text;
			s.FldEmail = this.mailBox.Text;
			s.FldPhone = this.phoneBox.Text;
			s.FldDate = DateTime.Now;
			s.FldContact = Convert.ToInt16(this.contactThru.SelectedItem.Value);
			this.sid.Text = [b]s.Add()[/b].ToString();
			this.article.Visible = true;
			this.LoadCategories();
		}
[/red]

Det är här jag bara anropar s.Add() för att spara mitt nya object och den i sin tur använder ORMappern för att spara objectet.

Det blev ju ganska mycket kod i detta inlägg men jag hoppas att jag kan få lite åsikter på hur jag byggt upp det hela.

mv icaaq

Medlem sedan dec. 19996 522 inlägg
#16

En fråga, vad tjänar du på att fylla BusinessObjects i ett annat lager än BOL, Vad gör du mer i ditt BLL?

Medlem sedan apr. 2004778 inlägg
#17

Den här frågan är lite riktad till Gladh.

Hur sköter din OR Mapper nestlade objekt?
T.ex.

class Order
  Private  _id As Integer
  Private _userid as Integer
  Private  _rows As OrderRows
End class

class OrderRow
  Private _id
  Private _productid
  Private _number
end class

Genererar du koden på nåt sätt eller kodar du dina klasser för hand och då fixar sådana här saker?

Erka:
Att fylla BusinessObjects med sin BusinessLogic kan man göra i det exempel som Gladh ger ovan, när du har "dumma" objekt, alltså att de endast innehåller properties och inte vill anropa OR Mappern från sitt presentationslager. Eller så låter man OR Mappern ta över BusinessLogic lagret också.
Men Isaaq, då ska inte Add funktionen finnas i objektet, endast properties.

Medlem sedan okt. 20005 273 inlägg
#18

PDahlen skrev:

Men Isaaq, då ska inte Add funktionen finnas i objektet, endast properties.

Hur menar du då? exempel tack ;)

mv icaaq med c ;)

Medlem sedan apr. 2004778 inlägg
#19

Ett "dumt" objekt har bara properties, inga funktioner.
Add funktionen skall därför ligga i BusinessLogic lagret i en generisk funktion som tar ett objekt och anropar OR Mappern. Istället för att som nu ha en Add funktion i varje objekt och det enda som skiljer dessa Add funktioner är objekttypen.

Medlem sedan maj 20012 812 inlägg
#20

Patrik mina O/R Mapper bryr sig inte om nestlade object (inte än iallafall då jag inte skrivet en generator till min nya O/R Mapper (den är tuff :))) så jag löser problemet för hand genom lazy-loading.

Typ så här:

class OrderHeader(){
 private long _OrderHeaderId;
 private OrderRows _OrderRows;

 public OrderRows OrderRows{
  get{
   if(_OrderRows == null)
    _OrderRows = new OrderRows(_OrderHeaderId);

   return _OrderRows
  }
 }  
}

Vid en kodgenering så hade jag satt ett attribute på medlemsvariablen _OrderRows, som hade beskrivit att den skulle använd sig av LazyLoading och _OrderHeaderId för att skapa sitt OrderRows Object.

Jag använder alltid LazyLoading. IMHO så är det korkat att hämta någon data från databasen och jag inte vet att jag skall använda den.

- M

273 ms totalt · 4 externa anrop · v20260731065814-full.868a69e5
128 ms — deklarationer (db)
0 ms — hämta statistik (cache)
142 ms — hämta tråd, inlägg och bilagor (db)
127 ms — ändringar (db)