Need reality check

Need reality check

Old forum URL: forums.lhotka.net/forums/t/1970.aspx


ajj3085 posted on Wednesday, December 13, 2006

Ok,

I have a business object (a readonly object) that has a property.  Basically its a boolean indicating if there is a current support contract associated with the object.

We need to allow purchasing of new support contracts.  My first thought is that the UI can check this property before it even tries to create the support contract.. but this also feels like a business rule.

So, should I have the UI check that property and alert the user, or should I just always call the NewSupportContract factory method (which accepts the object, and checks the same property and will throw an exception if there already is a contract).

Andy

SonOfPirate replied on Wednesday, December 13, 2006

I guess I would consider both viable.  Since you already have the property, you can easily use it to disable whatever command button/link you have in the UI that triggers the operation (i.e. no "purchase support contract" button/link).

But, I would also apply the "business rule" that you describe in your factory to ensure that a new contract is not created (and assigned?) to an object that already has one.  I think this is totally consistent with the application of business rules and ensures that the rule is followed even if the UI doesn't block the action.

Then, you'd have the option of using the exception that is thrown as a trigger for a message on the UI.  Best of both worlds.

HTH.

 

twistedstream replied on Wednesday, December 13, 2006

There might be another approach that you want to consider as well.  One of the concepts that CSLA.NET seems to encourage is to not throw exceptions the moment a business rule is violated.  This is especially true when validation rules are broken during data entry.  The only time an exception related to a business rule occurs is if one attempts to commit (Save) an invalid business object (or object model) to the database, and BusinessBase.Save throws this automatically. 

Instead of throwing that exception, you would have a broken rule in your BrokenRulesCollection that would be keeping your business object in an invalid state.  The IsValid property could then be tied to whatever button would be used to Save or commit the business object.

~pete

SonOfPirate replied on Wednesday, December 13, 2006

I guess the question is how Andy is using the factory.  Sounds to me like the Contract factory accepts the BO as a parameter which means that the operation is not triggered by a call to the BO itself.  If the operation is performed by something like:

ContractFactory.CreateNewContract(myBO);

Then the business rule is being tested outside of the BO in question if the factory checks the boolean property and blocks creation of the new contract when the condition is invalid (possibly raising an exception).  On the other hand, if the factory delegates to a method on the BO for the actual assignment, such as (in the factory):

Contract newContract = new Contract();
myBO.Contract = newContract;

or,

myBO.AssignContract(newContract);

then the rule could be checked in the BO but you suffer added performance and resource requirements becuase you've created a contract object that you won't be using.

Now, if the operation is performed by something like this (in the BO):

public void AddContract()
{
    // Maybe Contract's set accessor has private or protected scope so this method must be called
    Contract = ContractFactory.CreateNewContract();
}

then it is easy to apply the rule within the BO and have it exposed as a broken rule rather than an exception.

I agree that it is sometimes more graceful to use the validation rules, but in this case it will depend (IMO) on how the operation is being performed and whether the condition is a "show-stopper" or not.  If so, then an exception is certainly appropriate.

 

xal replied on Wednesday, December 13, 2006

I would definitely throw the exception in the bo layer, since that's where you can validate that.
You could also opt to just have a business rule there that will always return false if appropiate, but that's just a way of avoiding the exception. Personally I'd opt to throw the exception.

Anyway, that is no excuse for not blocking the button in the ui. You have the information you need in order to make the ui more friendly and avoid the exception. Some people may consider that replicating business rules in the UI, but to me that's just part of making the UI friendlier.

You could disguise that by having a method in your readonly object (or in the root object) like "CanCreateThatObject", but that's just overkill. You can always bind your button to the property in the readonly item, and that's it!

Andrés

ajj3085 replied on Thursday, December 14, 2006

Andres,

I agree, this wasn't an xor question.  The exception will be thrown, because the only way to really know if there's a support contract is to look it up in the database, so this is one DP_C method that actually doesn't use RunLocal for me.

Using the boolean to disable the button would just provide a friendlier UI as you mention.  My only concern was that at some point creating a new contrac might be more limiting.  For example, we may not allow conracts to be created for a product which is more than 10 years old.  In that case, whether or not a contract exists is not sufficent to decide if any buttons should be disabled.

I've also thought of something this morning; there really is no 'editing' of a support contract.  You can create it new, or ask it to extend itself (the amount of extension is up to the BO, not the user).  That's it.. so as I mentioned in my other relplies, perhaps these two needs are best served by command objects?

What does everyone think of that?

twistedstream replied on Thursday, December 14, 2006

So, if I understand this right, a contract can either be extended (if one already exists in the database) or created (if ones doesn't), with the possibility of more future rules that disallow contracts to be created for old products. 

What if you had a single factory method on Contract class called Fetch that, like you suggested, used a command object to query the database to determine one of the three possible outcomes:

  1. A contract exists in the database; Fetch would return a Contract instance that represents the existing contract.  You said that existing contracts could only be updated and the update changes were determined by the BO.  Therefore this Contract instance could have those new updates applied, but the Contract instance would be dirty and not committed to the database until Save was called.
  2. No existing contract exists; Fetch would return a Contract instance that represented a new contract (IsNew = true).
  3. No existing contract exists but the product is too old (so a new contract cannot be created).  This option might be a little tricky.  You could return null, but that doesn't tell the user why a contract could not be created.  Or you could return a new Contract instance that had a broken rule explaining why the contract cannot be committed back to the database.  Either way, you'll be preventing the user form actually saving a new contract back to the database.

I suppose another option would be to provide a factory method that just returns the command object.  Then the UI could examine the command object to determine what was possible.  Then that command object instance could be used to create an actual Contract instance.

~pete

 

ajj3085 replied on Thursday, December 14, 2006

twistedstream:
So, if I understand this right, a contract can either be extended (if one already exists in the database) or created (if ones doesn't), with the possibility of more future rules that disallow contracts to be created for old products.


Yes, that's right, although the addtional rules is just something I'm thinking of for the future.. no one else has mentioned anything like such a rule.  But I am considering the possiblity to help keep me from desiging my objects improperly.

You're right about the three options (although three is just there to force me to think about maintance), and those options are the ones that exist now.  Given that, does it make sense to have an object that can only be fetched or created, and forcing save. 

That is, what is the purpose of keeping the object in memory, since it cannot be edited at all?  There will be no screen to show what the contract will look like until after it already exists.

Andy


ajj3085 replied on Thursday, December 14, 2006

Pirate,

My factory method is the as you describe first, it accepts an object of ISerialNumber (because contracts are tied to serial numbers).

Once its created, you really can't do any normal editing.  You can save it if its new, or call Extend and then save if its existing (the expiration date is decided by code, and cannot be entered manually).

Maybe a few command objects would be better in this case?

ajj3085 replied on Thursday, December 14, 2006

Well the only thing here is that there really is no editing for the BO in question (the new support contract).  I thought about bringing up a screen and having a way to assign a serial number to the new contract, but that just seemed like overkill.  I'm also not sure if I should have created a few command objects.  The only thing you can do to a support contract independately is create it or extend it.  Other than that, there's  no behaviors. 

Copyright (c) Marimer LLC