patterncsharpMinor
Is the solution design optimal? C# 3 - Tier
Viewed 0 times
theoptimaldesigntiersolution
Problem
I have the following solution structure. This is a business domain (for an amount transfer operation in bank account) which will be called by a WCF service. Is the solution structuring correct?
-
Data Acess Layer does not create domain objects. It just pass database record wrapped in another simple object (DataEntities.AccountRow). Is it a good/standard approach?
-
Manager and domain objects are in two different layers. Is it okay?
-
A layer “DTOforServiceCommunication” is created for communicating with WCF service.
-
Is DTOforServiceCommunication and DataEntities are redundant or a good practice?
-
What are the improvement points for this solution structure?
Note: The service mentioned above will be used by multiple business functions (clients). Since the SOA is not object oriented, we cannot pass business domain objects across the boundary.
// TransferDTO
// AccountRow
// AccountManager
```
namespace BusinessManager
{
public class AccountManager
{
public void TransferAmount(DTOforServiceCommunication.TransferDTO transferDTO)
{
//DAL does not create domain objects. It just pass database record wrapped in another simple object
DAL.AccountDAL accountDAL = new DAL.AccountDAL();
DataEntities.AccountRow row = accountDAL.GetAcocunt(transferDTO.UserID, transferDTO.FromAccounutNumber);
DomainObject.IBankAccount bankAccount = null;
if (String.Equals(row.AccountType, "Savings"))
{
bankAccount = new DomainObjec
-
Data Acess Layer does not create domain objects. It just pass database record wrapped in another simple object (DataEntities.AccountRow). Is it a good/standard approach?
-
Manager and domain objects are in two different layers. Is it okay?
-
A layer “DTOforServiceCommunication” is created for communicating with WCF service.
-
Is DTOforServiceCommunication and DataEntities are redundant or a good practice?
-
What are the improvement points for this solution structure?
Note: The service mentioned above will be used by multiple business functions (clients). Since the SOA is not object oriented, we cannot pass business domain objects across the boundary.
// TransferDTO
namespace DTOforServiceCommunication
{
public class TransferDTO
{
public int UserID { get; set; }
public int FromAccounutNumber { get; set; }
public int ToAccountNumber { get; set; }
public int AmountToTransfer { get; set; }
}
}// AccountRow
namespace DataEntities
{
public class AccountRow
{
public int AccountNumber { get; set; }
public string AccountType { get; set; }
public int Duration { get; set; }
public int DepositedAmount { get; set; }
}
}// AccountManager
```
namespace BusinessManager
{
public class AccountManager
{
public void TransferAmount(DTOforServiceCommunication.TransferDTO transferDTO)
{
//DAL does not create domain objects. It just pass database record wrapped in another simple object
DAL.AccountDAL accountDAL = new DAL.AccountDAL();
DataEntities.AccountRow row = accountDAL.GetAcocunt(transferDTO.UserID, transferDTO.FromAccounutNumber);
DomainObject.IBankAccount bankAccount = null;
if (String.Equals(row.AccountType, "Savings"))
{
bankAccount = new DomainObjec
Solution
Data Acess Layer does not create domain objects. It just pass database record wrapped in another simple object (DataEntities.AccountRow). Is it a good/standard approach?
Repository classes should create domain objects. The domain objects may or may not look the same as the DB entities.
If you mean that you write your DAL by yourself (and not using a ORM): Stop with that. It's a waste of time.
Manager and domain objects are in two different layers. Is it okay?
Use the term WCF service and not manager. Yes. I consider WCF services to be an UI layer since it's the interface to the calling user/client.
A layer “DTOforServiceCommunication” is created for communicating with WCF service.
The name doesn't matter as long as you understand the difference between a domain object and a DTO.
Is DTOforServiceCommunication and DataEntities are redundant or a good practice?
Good practice. The DTO's should never change since it would break all clients that use them.
What are the improvement points for this solution structure?
No need for a
Update
Repository class using vanilla ADO.NET:
Usage:
Update2
Added a
The
You could also break out the
Repository classes should create domain objects. The domain objects may or may not look the same as the DB entities.
If you mean that you write your DAL by yourself (and not using a ORM): Stop with that. It's a waste of time.
Manager and domain objects are in two different layers. Is it okay?
Use the term WCF service and not manager. Yes. I consider WCF services to be an UI layer since it's the interface to the calling user/client.
A layer “DTOforServiceCommunication” is created for communicating with WCF service.
The name doesn't matter as long as you understand the difference between a domain object and a DTO.
Is DTOforServiceCommunication and DataEntities are redundant or a good practice?
Good practice. The DTO's should never change since it would break all clients that use them.
What are the improvement points for this solution structure?
No need for a
userId in the WCF interfaces or the DTOs unless you want to let everyone be able to look at everybody elses accounts. Use the userid provided during authentication.Update
Repository class using vanilla ADO.NET:
public class AccountRepository
{
Dictionary _accountClasses = new Dictionary{{"savings", typeof(SavingsAccount)}, {"fixed", typeof(FixedAccount)}};
List dbRecords = new List()
{
new DataEntities.AccountRow{AccountNumber=1,AccountType="Savings",Duration=6,DepositedAmount=50000},
new DataEntities.AccountRow{AccountNumber=2,AccountType="Fixed",Duration=6,DepositedAmount=50000}
};
public T Get(int userID, int accountNumber) where T : Account
{
var sql = "blabla";
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
return null;
var account = CreateRow(reader);
if (account.GetType() != typeof(T));
throw new InvalidOperationException("The requested account was not of the specified type");
return account;
}
}
}
public IEnumerable FindMyAccounts(int userId)
{
var sql = "blabla";
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
List accounts = new List();
while (reader.Read())
{
var account = CreateRow(reader);
accounts.Add(account);
}
return accounts;
}
}
}
// IDataRecord = a row in a DataReader
public Account CreateRow(IDataRecord record)
{
// might want to check so that the account got a
Type type;
if (!_accountClasses.TryGetValue(record["AccountType"], out type))
throw new InvalidOperationExcpetion("Account type do not exist: " + record["AccountType"]);
var account = (Account)Activator.CreateInstance(record);
// fill record here
}
}
public class Account
{
}
public class SavingsAccount : Account
{
}
public class FixedAccount : Account
{
}Usage:
account = _repository.Get(1, "kdkdkdkdkd");Update2
Added a
Find method to show that the actual mapping is reused by all methods. The
Find method will also return different types of accounts in the same list.You could also break out the
ExecuteReader parts into two methods (one to fetch one item and one to fetch collections) to reuse the code more.Code Snippets
public class AccountRepository
{
Dictionary<string, Type> _accountClasses = new Dictionary<string, Type>{{"savings", typeof(SavingsAccount)}, {"fixed", typeof(FixedAccount)}};
List<DataEntities.AccountRow> dbRecords = new List<DataEntities.AccountRow>()
{
new DataEntities.AccountRow{AccountNumber=1,AccountType="Savings",Duration=6,DepositedAmount=50000},
new DataEntities.AccountRow{AccountNumber=2,AccountType="Fixed",Duration=6,DepositedAmount=50000}
};
public T Get<T>(int userID, int accountNumber) where T : Account
{
var sql = "blabla";
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
return null;
var account = CreateRow(reader);
if (account.GetType() != typeof(T));
throw new InvalidOperationException("The requested account was not of the specified type");
return account;
}
}
}
public IEnumerable<Account> FindMyAccounts(int userId)
{
var sql = "blabla";
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
List<Account> accounts = new List<Account>();
while (reader.Read())
{
var account = CreateRow(reader);
accounts.Add(account);
}
return accounts;
}
}
}
// IDataRecord = a row in a DataReader
public Account CreateRow(IDataRecord record)
{
// might want to check so that the account got a
Type type;
if (!_accountClasses.TryGetValue(record["AccountType"], out type))
throw new InvalidOperationExcpetion("Account type do not exist: " + record["AccountType"]);
var account = (Account)Activator.CreateInstance(record);
// fill record here
}
}
public class Account
{
}
public class SavingsAccount : Account
{
}
public class FixedAccount : Account
{
}account = _repository.Get<SavingsAccount>(1, "kdkdkdkdkd");Context
StackExchange Code Review Q#9585, answer score: 4
Revisions (0)
No revisions yet.