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.

HardCode
  • 614
  • 4
  • 12
  • 1
    I’m not familiar with the CQR abbreviation, what does it stand for? – Rik D Sep 08 '21 at 15:37
  • @RikD I meant CQS. I'll update the post. Thanks. – HardCode Sep 08 '21 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. – Greg Burghardt Sep 08 '21 at 20:34
  • @RikD: maybe the OP meant CQRS (Command-Query Responsibility Segregation)? [CQS](https://www.martinfowler.com/bliki/CommandQuerySeparation.html) is a little different, and I thought CQRS involved commands like the OP mentioned in this question. – Greg Burghardt Sep 09 '21 at 02:24
  • @GregBurghardt CQS is Command Query Separation, which also involves commands. They are similar, but different. https://martinfowler.com/bliki/CommandQuerySeparation.html – HardCode Sep 09 '21 at 12:56
  • @HardCode: that is the same page I linked to. CQS talks about commands as methods on an object that modify state being separate from methods that return a value. CQ**R**S involves splitting classes up when logic that modifies state or returns a value becomes complex enough that it warrants specialized classes. They are different, but related concepts. – Greg Burghardt Sep 09 '21 at 15:37
  • Validation of data entry is one of those things where everyone starts out trying to make it pure and clean and (if they're smart about it) eventually gives up, because there are so many competing concerns. See [this answer](https://softwareengineering.stackexchange.com/a/351662/115084). – John Wu Sep 09 '21 at 16:21

2 Answers2

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.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • 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 08 '21 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. – Robert Bräutigam Sep 08 '21 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
  }
}
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 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 08 '21 at 20:10
  • hmm I dont think there is a simple answer to that. – Ewan Sep 08 '21 at 20:15
  • 1. re your CQS setup, its arguable that the Validate logic should be on the command – Ewan Sep 08 '21 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 08 '21 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 08 '21 at 20:20
  • I think an IsValid property on a command is pretty normal, but you have opened a can of worms unrelated to your "how do i show business logic on a view" question – Ewan Sep 08 '21 at 20:21