I have created a POCO model class and a repository class which handles persistence. Since the POCO cannot access the repository, there are lots of business logic tasks in the repository which doesn't seem right. From what I have read, it looks like I need a service layer that sits between the UI consumers and the repository layer. What I am not sure of is exactly how it is supposed to work...
In addition to the service layer, should there also be a separate business logic layer, or is that the role of the service layer?
Should there be one service per repository?
Is the service layer the only way that the UI can instance the model objects or does the repository provide the new model instance to the service?
Do I put my parameter, model and other validations in the service layer that do things like check to make sure a input is valid and that a item to update exists in the database before updating?
Can the model, repository and UI all make calls to the service layer, or is it just for the UI to consume?
Is the service layer supposed to be all static methods?
What would be a typical way to call the service layer from the UI?
What validations should be on the model vs the service layer?
Here is some sample code for my existing layers:
public class GiftCertificateModel
{
public int GiftCerticiateId {get;set;}
public string Code {get;set;}
public decimal Amount {get;set;}
public DateTime ExpirationDate {get;set;}
public bool IsValidCode(){}
}
public class GiftCertificateRepository
{
//only way to access database
public GiftCertificateModel GetById(int GiftCertificateId) { }
public List<GiftCertificateModel> GetMany() { }
public void Save(GiftCertificateModel gc) { }
public string GetNewUniqueCode() { //code has to be checked in db }
public GiftCertificateModel CreateNew()
{
GiftCertificateModel gc = new GiftCertificateModel();
gc.Code = GetNewUniqueCode();
return gc;
}
}
UPDATE: I am currently using web forms and classic ADO.NET. I hope to move to MVC and EF4 eventually.
UPDATE: Big thanks to @Lester for his great explanation. I now understand that I need to add a service layer for each of my repositories. This layer will be the ONLY way the UI or other services can communicate with the repository and will contain any validations that do not fit on the domain object (eg - validations that need to call the repo)
public class GiftCertificateService()
{
public void Redeem(string code, decimal amount)
{
GiftCertificate gc = new GiftCertificate();
if (!gc.IsValidCode(code))
{
throw new ArgumentException("Invalid code");
}
if (amount <= 0 || GetRemainingBalance(code) < amount)
{
throw new ArgumentException("Invalid amount");
}
GiftCertificateRepository gcRepo = new GiftCertificateRepository();
gcRepo.Redeem(code, amount);
}
public decimal GetRemainingBalance(string code)
{
GiftCertificate gc = new GiftCertificate();
if (!gc.IsValidCode(code))
{
throw new ArgumentException("Invalid code");
}
GiftCertificateRepository gcRepo = new GiftCertificateRepository();
gcRepo.GetRemainingBalance(code);
}
public SaveNewGC(GiftCertificate gc)
{
//validates the gc and calls the repo save method
//updates the objects new db ID
}
}
Questions
Do I add the same (and possibly more) properties to the service as I have on my model (amount, code, etc) or do I only offer methods that accept GiftCertificate objects and direct parameters?
Do I create a default instance of the GiftCertificate entity when the Service constructor is called or just create new ones as needed (eg - for validation methods in the service that need to call validation methods in the entity? Also, same question about creating a default repository instance...?
I know i expose the functionality of the repo via the service, do I also expose the methods from the entity as well (eg - IsValidCode, etc)?
It is ok for the UI to simply create a new GiftCertificate object directly without going through the service (eg - to call parameter validation methods from the entity). If not, how to enforce it?
On the UI layer, when I want to create a new gift certificate, do I call the model/service validations (like IsValidExpirationDate, etc) directly from the UI layer OR do I hydrate the object first, then pass it in to be validated and then return some sort of validation summary back to the UI?
Also, if I want to Redeem from the UI layer, do I first call the model/service validation methods from the UI to give user feedback and then call the Redeem method which will run the same checks again internally?
Example for calling service to do a Redeem operation from UI:
string redeemCode = RedeemCodeTextBox.Text;
GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate(); //do this to call validation methods (should be through service somehow?)
if (!gc.IsValid(redeemCode))
{
//give error back to user
}
if (gcService.GetRemainingBalance(redeemCode) < amount)
{
//give error back to user
}
//if no errors
gcService.Redeem(code,amount);
Example for creating a new Gift certificate from UI:
GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate();
if (!gc.IsValidExpDate(inputExpDate))
{
//give error to user..
}
//if no errors...
gc.Code = gcService.GetNewCode();
gc.Amount = 10M;
gc.ExpirationDate = inputExpDate;
gcService.SaveNewGC(gc);
//method updates the gc with the new id...
Something feels wrong about way GCs are being created and how the validations are separated between entity/service. The user/consumer should not have to be concerned with what validations are in which place... advice?