3 ms·
The problem is that people never properly understood which operations were supposed to go on objects, and they just started accumulating everything that was eve
by jdmichal 7y ago
The problem is that people never properly understood which operations were supposed to go on objects, and they just started accumulating everything that was even loosely related. My favorite example is registering for university courses. So you have a Student object and a Course object... Which one does the register function go on?
Objects should only contain those methods which are required for the invariants of that object. A course registration function is a workflow that involves two types, but does not control their invariants, so it belongs on neither. Most business logic falls into this bucket, and that bucket really works best as a functional paradigm where one can freely recombine the logic to create new workflows.
(The worst answer I've seen is that both end up with a register method, with one just reversing the parameters. So Student.register(Course) just calls Course.register(Student).)
- kingdomcome50 7y agoThere are well-defined patterns for such behavior. Domain services are introduced to protect invariants _between_ entities. Implementation is simple really. Student “fills out course registration form” (VO) and then Course “completes registration with filled out registration form”. Essentially the behavior of registering for the course is distributed between Student and Course as appropriate. The point I’m trying to make is that the problem is not inherit in OOP. The problem is that domain modeling is hard. If a developer cannot synthesize the above “solution”, I’m not sure a different paradigm is going to help.
- jdmichal 7y agoAnd why does a Course register a student? Why does it know what a "course registration form" is? None of these things have anything to do with the Course object or is invariants... The Course is just one of many parameters into the workflow. Why is it therefore responsible for the actual act of registration? In real life, the aptly named "student registrar" is responsible for performing the task. But they're not an entity within this system... Rather they are the context in which this system is running. So a non-bound function actually makes perfect sense... It's bound to the top-level registrar context of the system. In BPMN, the registrar would be one swimlane, and the registration the process, with additional swimlanes for other actors like the student. The registrar still owns the process, but BPMN allows for other actors to also play parts within the system. A relational view would introduce a Registration which would join a Course and Students. The workflow would create a Registration, which defacto means that an individual Registration cannot be responsible for executing this.
- jstimpfle 7y ago> Most business logic falls into this bucket, and that bucket really works best as a functional paradigm There is nothing I can see here that calls for FP. You are still mutating the state of the world. Just use a free standing function. That's like a method on "The world", which is the object where we should put most functions. Programming is easy. (Not: all the technical decisions behind it)
- jdmichal 7y agoI explicitly call out the reasoning for using functional concepts here: recombination into new workflows. For instance, a student may only be able to register for 15 credit hours or less. However, an advisor can register a student for up to 20 credit hours. If done properly, this should be a very simple change within the workflow... Just swap out the verification function that's composed into the workflow.
- jstimpfle 7y agoI still don't see what's functional about this. Functional is about higher-order functions, currying, and so on. I'm not positive most business logic profits from that. What you describe can be achieved by swapping out a number (or more generally, data). Of course, we can come up with a scheme that requires two validation functions that have more distinctive features than just some number's values. And in this case that might be implemented by parameterizing the function parameter. But, it could also be implemented with a switch for example, and often this approach is more maintainable. And then, you can also have pointers to functions in conventional programming languages. In general, the need for functional approaches is vastly overblown.
- jdmichal 7y agoI mean, it all boils down to assembly in the end... Why not just do everything in assembly? I'm finding little too your arguments other than what seems to be a pre-existing bias towards functional approaches being "overblown", along with various alternatives which of course exist, because every programming paradigm is capable of implementing arbitrary logic. Some abstractions will help you do certain things. If you find yourself constantly duplicating and tweaking the same axiomatic logic to do different things, functional approaches are generally a more helpful abstraction than others.