Sebelumnya saya sudah membahas penggunaan Repository pattern dalam ASP.NET MVC. Dalam posting kali ini saya akan membahas tentang Service Layer, sebuah architectural pattern lain yang cukup terkenal selain Repository pattern. Pattern ini juga disebutkan oleh Martin Fowler dalam bukunya Pattern of Enterprise Application Architecture. Seperti dalam membahas Repository pattern, saya tidak akan banyak menguraikan teori di sini.
Dalam konteks pembahasan ASP.NET MVC controllers, Repository, dan Service Layers, secara diagram saya menggambarkan arah dependency-nya sebagai berikut:
Jadi, Controllers punya dependensi terhadap Service Layers, dan Service Layers punya dependensi terhadap Repositories. Perhatikan bahwa Controllers tidak secara langsung punya dependensi terhadap Repositories, melainkan melalui Service Layers. Lalu kenapa kita menempatkan Service Layers sebagai middle-man di situ? Bukannya dari Controllers bisa langsung memakai Repositories?
Sesuai dengan namanya, Service Layers menyediakan service bagi component atau aplikasi consumer-nya, dalam hal ini ASP.NET MVC Controllers. Tentu saja jenis layanannya bisa bermacam-macam, tidak hanya berhubungan dengan task-task CRUD (Create, Retrieve, Update, dan Delete) data ke database saja yang nantinya akan dilakukan oleh Repositories. Misalnya aplikasi kita butuh untuk mengirimkan email, kita bisa membuat Service Layer yang melakukan pengiriman email ini. Ada lagi misalnya kebutuhan untuk menghubungkan aplikasi kita dengan SharePoint, SAP, Siebel, atau barang-barang mainan enterprise lain, kita juga bisa menggunakan Service Layer.
Oleh karena itu saya melihat Service Layer ini sebagai tempat yang cocok untuk meletakkan business logic. ASP.NET Controllers sesuai dengan best practice dan guideline dari Microsoft sendiri harus dibuat se-thin mungkin, dan bukan tempat yang tepat untuk meletakkan business logic. Repositories di sisi lain, sesuai dengan namanya hanya melakukan task-task yang berhubungan dengan underlying data mechanism. Meletakkan business logic dan fungsi-fungsi lain yang tidak berhubungan dengan data mechanism tentu saja tidak cocok.
OK, sekarang saya akan tambahkan Service Layers ini dalam project Models dari solusi ASP.NET MVC yang sudah kita buat sebelumnya. Penempatan Service Layers ini bisa dilakukan dalam satu project tersendiri atau jadi satu project bersama Repositories. Untuk contoh berikut ini saya menempatkannya dalam satu project bersama Repositories.
Berikut contoh source code dari IContactService dan ContactService. Dalam contoh tersebut selain melakukan task CRUD melalui repository yang berkaitan, service layer ini perform task lain berupa pengiriman email.
// Copyright (C) 2009 by Agus Suhanto [agus.suhanto@mvps.org]
// For more information please visit http://suhanto.com
//
#region
using CompanyProfile.Models.Entities;
#endregion
namespace CompanyProfile.Models.Services
{
public interface IContactService : ISettingAwareService
{
void SendContactMessage(ContactMessage message);
}
}
// Copyright (C) 2009 by Agus Suhanto [agus.suhanto@mvps.org]
// For more information please visit http://suhanto.com
//
#region
using System;
using System.Text;
using CompanyProfile.Models.Entities;
using CompanyProfile.Models.Helpers;
using CompanyProfile.Models.Properties;
using CompanyProfile.Models.Repositories;
#endregion
namespace CompanyProfile.Models.Services.Impl
{
public class ContactService : IContactService
{
private readonly IMessageRepository messageRepository;
public ContactService(IMessageRepository messageRepository)
{
this.messageRepository = messageRepository;
}
#region IContactService Members
public Setting Setting { get; set; }
public void SendContactMessage(ContactMessage message)
{
if (Setting == null) throw new ArgumentException(Resource.SettingIsNull);
message.MessageId = Guid.NewGuid();
message.MessageDate = DateTime.Now;
messageRepository.SaveMessage(message, Setting);
MailHelper.SendEmail(
Setting.MailFrom,
Setting.MailTo,
string.Format(Resource.ContactFormEmailSubject, message.FromName),
ConstructMailBody(message),
Setting.SmtpServer,
Setting.SmtpPort,
Setting.SmtpRequiresAuth,
Setting.SmtpUserName,
Setting.SmtpPassword,
true /* isAsync */);
}
#endregion
private static string ConstructMailBody(ContactMessage message)
{
var sb = new StringBuilder();
sb.AppendFormat(Resource.ContactFormFromName, message.FromName);
sb.AppendFormat(Resource.ContactFormFromEmail, message.FromEmail);
sb.AppendFormat(Resource.ContactFromSubject, message.Subject);
sb.AppendFormat(Resource.ContactFormBody, message.Message);
return sb.ToString();
}
}
}
OK sekarang kita akan menggunakan ContactService tersebut dalam controller kita yang namanya secara kebetulan sama yaitu ContactsController. Berikut ini source code untuk ContactsController.
// Copyright (C) 2009 by Agus Suhanto [agus.suhanto@mvps.org]
// For more information please visit http://suhanto.com
//
#region
using System.Web.Mvc;
using CompanyProfile.Models.Entities;
using CompanyProfile.Models.Helpers;
using CompanyProfile.Models.Services;
using CompanyProfile.Web.Properties;
#endregion
namespace CompanyProfile.Web.Controllers
{
[HandleError]
[CultureIdFilter]
public class ContactsController : ControllerBase
{
private readonly IContactService contactService;
public ContactsController(ISettingService settingService,
IContactService aboutService) : base(settingService)
{
contactService = aboutService;
contactService.Setting = Setting;
}
public ActionResult Index()
{
return View("index", GetLayout());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(ContactMessage message)
{
#region Validation
if (message.FromName.Trim().Length == 0)
ModelState.AddModelError("FromName", Resource.NameIsRequired);
if (message.FromEmail.Trim().Length == 0)
ModelState.AddModelError("FromEmail", Resource.EmailIsRequired);
if (!message.FromEmail.Trim().IsValidEmail())
ModelState.AddModelError("FromEmail", Resource.EmailIsInvalid);
if (message.Subject.Trim().Length == 0)
ModelState.AddModelError("Subject", Resource.SubjectIsRequired);
if (message.Message.Trim().Length == 0)
ModelState.AddModelError("Message", Resource.MessageIsRequired);
if (!ModelState.IsValid) return View("index", GetLayout());
#endregion
contactService.SendContactMessage(message);
return RedirectToRoute("Default",
new { controller = "Contacts", action = "Thanks" });
}
public ActionResult Thanks()
{
return View("thanks", GetLayout());
}
}
}
OK, that's it! Cukup mudah dan sederhana mengimplementasikan Service Layers dalam solusi ASP.NET MVC.