Searching pattern for user interface


Mengimplementasikan searching method untuk melakukan pencarian data bukanlah hal yang mudah. Bayangkan kita di assign task untuk mengimplementasikan searching departement dengan screen seperti dibawah ini:

Search Department
Name: [_______]
Age: from [__] to [__] year old
Budget: [ ]
{Search}

Data akses yang spesifik berhubungan dengan domain model di implementasikan dengan Repository pattern.  Cara umum yang biasa dilakukan orang adalah:

Lambda-Expression Repository

var result = departmentRepository.FindAll<Department>(
		x => x.Name.Contains(emailAddress) && x.StartDate > ageFrom
		&& x.StartDate > ageFrom && x.StartDate < ageTo || x.Budget < budget
	);

Aku suka dengan fitur Fluent diatas. Client API bisa dengan leluasa meng passing searching criteria ke parameter method tersebut. Tapi aku kurang nyaman dengan cara diatas, aku ga suka membiarkan searching criteria telanjang. Ketika aku hendak me-reuse criteria tersebut di screen lain tidak akan bisa. karena criteria di specify oleh client saat invoke method FindAll.

Repository Finder Methods

var result = departmentRepository.SearchByBlaBlaBloodyBla(name, ageFrom, ageTo, budget);

Oke, aku lebih suka cara ini. Implementasi searching terencapsulate pada method SearchByBlaBlaBloodyBla. Diimplementasinya bisa di lakukan pengecekan, saat namanya tidak null, nama dijadikan sebagai searching criteria dan selanjutnya. Lagi lagi aku kurang suka dengan cara ini, terkadang ada searching parameter/criteria banyak. Saat parameter searching banyak, pengecekan parameter di method itu juga banyak dan implementasi Repository pattern jadi terlalu gemuk.

Specification Pattern

...
public IEnumerable<Department> FindBySpec(ISpecification<Department> specs)
{
	return GetQuery<Department>().Where(criteria.IsSatisfiedBy).AsEnumerable();
}

...
var result = departmentRepository.Find<Department>(
	new IntenationalDepartmentSpec(),
	new DepartmentByNameSpecification("ce"))
);

Ow .. Specification pattern, sounds cool bro. Sesuai dengan kegunaan Repository yang di jelaskan oleh Martin Fowler di buku PoEAA

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction.

Tapi coba perhatikan code ini: GetQuery().Where(criteria.IsSatisfiedBy).AsEnumerable(); specification pattern itu bukan dibaca sebagai Expression Trees tapi di baca sebagai anonymous method / delegate yang return boolean. Code tersebut sama aja dengan code dibawah ini

foreach (var department in FindAll())
{
	if (specs.IsSatisfiedBy(department))
		yield return department;
}

Bayangkan ketika kamu memiliki record yang ribuan, record tersebut harus di load dari database / FindAll(), di simpan dalam memory lalu di search berdasarkan spesifikasi, yield return. Specification memang kelihatan keren tapi kurang efisien😀.

Solution

Aku biasanya membuat object Query untuk melakukan searching dengan criteria yang banyak/rumit. Dengan membuat query object kita mengikuti Object Oriented Principe yaitu:
Single Responsibility: Object ini kerjaanya cuma melakukan query berdasarkan criteria query yang terdapat pada property object itu
Command and Query: Repository Pattern biasanya aku gunakan untuk operasi write. Untuk operasi reaad/query/reporting dimana tidak merubah state domain model aku implementasikan dengan Query Pattern.

public class DepartmentQuery
{
	private readonly ObjectContext _context;
	private readonly IObjectSet<Department> _objectSet;

	public DepartmentQuery(ObjectContext context)
	{
		_context = context;
		_objectSet = _context.CreateObjectSet<Department>();
	}

	public string Name { set; get; }
	public decimal? Budget { set; get; }
	public DateTime? StartDate { set; get; }

	public IQueryable<Department> GetQuery()
	{
		var departments = _objectSet.AsQueryable() ;

		if (Name != null)
			departments = departments.Where(d => d.Name.Contains(Name));

		if (Budget != null)
			departments = departments.Where(d => d.Budget <= Budget);

		if (StartDate != null)
			departments = departments.Where(d => d.StartDate <= StartDate);

		return departments;
	}
}

Cukup sederhana, jika kamu ingin menambahkan parameter (orderBy, paging, parameter baru) tinggal tambahkan di Query Object tersebut.

NOTE: Tidak semua query cocok di implementasikan dengan ORM. Untuk query yang complex seperti join ke beberapa table, membutuhkan agreagate query, native query merupakan solusi yang sederhana,cepat dan tepat.

Reference

  1. Extensible Query with Specification
  2. The DAL should go all the way to the UI
  3. Repository Pattern

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s