3

Situation:

VB.NET WinForms application, using the WinForms as a presentation layer project. Another class library project containing the business layer, in the form of CQS and Service classes, plus a data access layer.

Summary:

I'm attempting to follow CQS for new features being added to the legacy application. Consider a product return log... I have a class for the Update Command, a class for the Create Command, and a class with two Query functions. The ProductReturnsService class contains the Public functions for the Windows Form to call Create, Update, ReadX, ReadY, etc. This class also contains private functions to validate the Update and Create commands.

Dilemma:

When updating an item on the product return log, I need the UI to disable several controls based on the current or changed data for the item being updated. For example, I have a CheckBox called "Completed", which will "close" that product return record and lock out changes to all the other fields. However, this CheckBox shouldn't be enabled unless, for example, Date Received has a value. The "Completed" CheckBox also shouldn't be enabled if the current user isn't a member of a certain security group in the system - even though that current user can enter data.

Question:

In OOP, it is my understanding that things like, "Can't check Completed until Receive Date has a value." and "Can't check Completed if not in the Manager role." are business rules. And it is my understanding that business rules belong in the business layer.

How do I expose those business rules to the UI, so that users aren't frustrated with returned validation error messages from the BL? How can I have the UI enable/disable controls according to the business rules in the BL? I also understand that following DRY, I wouldn't want to recode all the business rules in the Windows Form code page.

I can't wrap my head around how to implement one set of business rules that the BL acts on, as well as the UI. Currently, my BL validation is in the ProductReturnsService class, with one function to validate a Create Command, and another function to validate an Update Command, which I think makes sense because these validation functions are validating the data before it goes to the database. I would prefer the same kind of validation "in real time", interactively, on the UI.

7
  • 1
    I’m not familiar with the CQR abbreviation, what does it stand for?
    – Rik D
    Sep 8, 2021 at 15:37
  • @RikD I meant CQS. I'll update the post. Thanks.
    – HardCode
    Sep 8, 2021 at 15:41
  • "... things like, "Can't check Completed until Receive Date has a value." and "Can't check Completed if not in the Manager role." are business rules" — those would be used interface rules. The business rule would be "a product return cannot be completed without a return date, and only managers can complete product returns." The UI should respond to this logic by disabling the checkbox. Sep 8, 2021 at 20:34
  • @RikD: maybe the OP meant CQRS (Command-Query Responsibility Segregation)? CQS is a little different, and I thought CQRS involved commands like the OP mentioned in this question. Sep 9, 2021 at 2:24
  • @GregBurghardt CQS is Command Query Separation, which also involves commands. They are similar, but different. martinfowler.com/bliki/CommandQuerySeparation.html
    – HardCode
    Sep 9, 2021 at 12:56

2 Answers 2

1

The only way to get those decisions out of the UI, is to have the "business" objects generate / control the UI.

You have to decide, either the UI depends on business things, or the other way around. You must choose at least one, and if you're doing it wrong you will have both.

Traditionally people tend to want the business layer completely separate from the UI. This works if you need to support unknown UIs that you don't control. This is what a traditional "layered architecture" does.

If however you want to go in the direction of really having knowledge localized, and you control the UI anyway, you can let UI things into the "business" objects, to be able to keep UI logic free. Fair warning: people seem to have a natural aversion to this design for some reason, you'll be probably on your own if you attempt this. There's no other way to do what you're asking for though.

2
  • I was thinking along the lines of having the business rules in their own classes, and both the UI and the BL are dependent on (i.e. References in a .NET project) those object. This way, the business rules are defined once in the application, but usable by both the UI and BL.
    – HardCode
    Sep 8, 2021 at 17:14
  • 1
    That is just code-reuse it is not independence. Both the "business" and "UI" still have to know about this concept. Presumably also what data is needed and how to supply them, how to interpret the result, etc. Having knowledge localized is not just about having a single place for the actual algorithm, but not having any knowledge of it elsewhere either. Of course it is your decision whether you want that in this particular instance or not, just saying it is not what you asked for. Sep 8, 2021 at 17:24
0

It's not "Business Logic" is presentation layer.

For example you can imagine the same form done in multiple ways, a popup warning about the validation error, the checkbox being invisible, grayed out, validation after submit, ignoring invalid items, auto fill today's date etc etc

Sure the presentation choices you make are also "logic that is decided by the business" its just an arbitrary division between stuff that must always happen regardless of the presentation, and stuff that happens on some screens and not on others, or other imagined screens

As to how to implement ther functionality if you choose to put the logic in the business object, its simple enough you just have to expose a property on your business object that reflects its state

class Return
{
   DateTime ReturnDate
   bool Completed
   bool IsCompletable //Logic in BL
}

MyForm
{
  Return return
  CheckBox CompleteCheckBox

  //logic in BL
  OnChange() {  //you'll have to think about the method to choose here I dont remember my winForms
    if(return.IsCompletable) CompleteCheckBox.Disabled = true
  }

  //logic in View
  OnChange() { 
    if(return.ReturnDate == null) CompleteCheckBox.Disabled = true
  }
}
6
  • Ok, I think I understand this. Follow-up question: In your example, could/should I create methods in class Return such as ValidateCreate(cmd As ProductReturnsCreateCommand) and ValidateUpdate(cmd As ProductReturnsUpdateCommand), so that all validation code resides in class Return?
    – HardCode
    Sep 8, 2021 at 20:10
  • hmm I dont think there is a simple answer to that.
    – Ewan
    Sep 8, 2021 at 20:15
  • 1. re your CQS setup, its arguable that the Validate logic should be on the command
    – Ewan
    Sep 8, 2021 at 20:16
  • 2. If you go for Validate rather than a break down, IsCompleteable IsRefundable, IsSomethingElseable then you have a problem with how to translate the list of validation errors back into concrete things on your view
    – Ewan
    Sep 8, 2021 at 20:18
  • 3. adding these methods to Return arguably invalidates the command pattern in that you cant create new commands without altering Return
    – Ewan
    Sep 8, 2021 at 20:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.