Using events as error notification can solve these issues.
How it works:
A domain object contains an inner class ErrorNotifier. ErrorNotifier contains all the events that correspond to errors concerning the domain object. When the validate method is called on the domain object the business rules are checked, the appropriate error events are fired if errors are found, and any object subscribing to the error events will be notified.
When to use it:
You should use Error Eventing when you are not using a remote domain layer. Additionally, it is helpful when you need to validate only specific rules in a domain object.
Example: Booking a hotel room
Create a Reservation domain object in the domain layer.
public class Reservation
{
public const string GUESTS_INVALID = "Guests Invalid";
public const string CHECK_IN_INVALID = "Check In Invalid";
public const string HOTEL_NAME_INVALID = "Hotel Name Invalid";
private ReservationErrorNotifier _errorNotifier = new ReservationErrorNotifier();
private int _guests;
private DateTime _checkIn;
private string _hotelName;
public ReservationErrorNotifier ErrorNotifier
{
get
{
return _errorNotifier;
}
}
public int Guests
{
get { return _guests; }
set { _guests = value; }
}
public DateTime CheckIn
{
get { return _checkIn; }
set { _checkIn = value; }
}
public string HotelName
{
get { return _hotelName; }
set { _hotelName = value; }
}
public void Validate()
{
_errorNotifier.Validate(this);
}
public class ReservationErrorNotifier
{
public event ErrorEventHandler GuestsInvalid;
public event ErrorEventHandler CheckInInvalid;
public event ErrorEventHandler HotelNameInvalid;
public void Validate(Reservation reservation)
{
if (reservation.Guests <>Page 1 of your search is only going to collect check in date and number of guests. Therefore, the service layer contains a Search command that only accepts and validates check in date and guestspublic class SearchCommand : CommandLuckily, our reservation system is so simple the hotel will be selected on the next page and that will complete the reservation process. Therefore, we need to validate the existing reservation after adding the hotel to it. This is done with the Complete command located in the service layer.
{
private int _guests;
private DateTime _checkIn;
public SearchCommand(int guests, DateTime checkIn)
{
_guests = guests;
_checkIn = checkIn;
}
public void Execute()
{
Reservation reservation = new Reservation();
reservation.ErrorNotifier.GuestsInvalid += delegate { this.ErrorList.Add(Reservation.GUESTS_INVALID); };
reservation.ErrorNotifier.CheckInInvalid += delegate { this.ErrorList.Add(Reservation.CHECK_IN_INVALID); };
reservation.Guests = _guests;
reservation.CheckIn = _checkIn;
reservation.Validate();
this.Result = reservation;
}
}Both SearchCommand and CompleteCommand inherit their Result and ErrorList properties from Command
public class CompleteCommand : Command
{
private Reservation _reservation;
private string _hotelName;
public CompleteCommand(Reservation existingReservation, string hotelName)
{
_reservation = existingReservation;
_hotelName = hotelName;
}
public void Execute()
{
Reservation reservation = new Reservation();
reservation.ErrorNotifier.GuestsInvalid += delegate { this.ErrorList.Add(Reservation.GUESTS_INVALID); };
reservation.ErrorNotifier.CheckInInvalid += delegate { this.ErrorList.Add(Reservation.CHECK_IN_INVALID); };
reservation.ErrorNotifier.HotelNameInvalid += delegate { this.ErrorList.Add(Reservation.HOTEL_NAME_INVALID); };
reservation.HotelName = _hotelName;
reservation.Validate();
}
}Finally, the command objects can be used in the presentation layer to display the errors after execution.
public abstract class Command
{
private List_errorList = new List ();
private object _result;
public ListErrorList
{
get
{
return _errorList;
}
}
public object Result
{
get { return _result; }
set { _result = value; }
}
}
Lastly, the unit tests used to drive this development.
[TestFixture]
public class ReservationTests
{
[Test]
public void NumberOfGuestsMustBeGreaterThanZero()
{
bool methodCalled = false;
Reservation reservation = new Reservation();
reservation.Guests = 0;
reservation.ErrorNotifier.GuestsInvalid += delegate { methodCalled = true; };
reservation.Validate();
Assert.IsTrue(methodCalled);
}
[Test]
public void ReservationCheckInDateFallAfterToday()
{
bool methodCalled = false;
Reservation reservation = new Reservation();
reservation.CheckIn = DateTime.MinValue;
reservation.ErrorNotifier.CheckInInvalid += delegate { methodCalled = true; };
reservation.Validate();
Assert.IsTrue(methodCalled);
}
[Test]
public void ReservationHotelCannotBeNull()
{
bool methodCalled = false;
Reservation reservation = new Reservation();
reservation.HotelName = null;
reservation.ErrorNotifier.HotelNameInvalid += delegate { methodCalled = true; };
reservation.Validate();
Assert.IsTrue(methodCalled);
}
}
[TestFixture]
public class SearchCommandTests
{
[Test]
public void InvalidGuestsAndCheckInErrorsAreAdded()
{
SearchCommand cmd = new SearchCommand(0, DateTime.MinValue);
cmd.Execute();
Assert.IsTrue(cmd.ErrorList.Contains(Reservation.GUESTS_INVALID));
Assert.IsTrue(cmd.ErrorList.Contains(Reservation.CHECK_IN_INVALID));
}
}
[TestFixture]
public class CompleteCommandTests
{
[Test]
public void InvalidGuestsAndCheckInErrorsAreAdded()
{
CompleteCommand cmd = new CompleteCommand(new Reservation(),null);
cmd.Execute();
Assert.IsTrue(cmd.ErrorList.Contains(Reservation.GUESTS_INVALID));
Assert.IsTrue(cmd.ErrorList.Contains(Reservation.CHECK_IN_INVALID));
Assert.IsTrue(cmd.ErrorList.Contains(Reservation.HOTEL_NAME_INVALID));
}
}
Source available here (Visual Studio 2005 Beta 2 April release).
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.