Registration is definitely not a property of the student.
It's always a good idea in these situations to appeal to real life. The actual business will point the way of the business simulation. In the real world we don't ask students to register for a course. Instead students ask the Registrar to register for a course. When the Registrar makes her decision she considers far more than just the internal state of the Student; she considers (1) does the course have any available seats? (2) has the Student met all the course pre-requisites and, most importantly, (3) has the student paid his tuition and is he even a valid member of the university community.
What the Student does have is a history and a context -- that is, a state -- that must be considered when registering for courses. The student may also have preferences -- courses he wants to register for.
The language of the business should guide these decisions always. A student submits a request tfor a course and it is the office of that accepts or denies this request.
Take this thinking to the end and realize that it leads to freestanding functions. In general all the context of the program is needed to execute a functionality. It's not like the registration office owns all the students. It's not like a registration wouldn't change the student's context. Students are both an independent and related concept. The proper object to call most things on is a "Global" object. Now instead of
Global.do_some_thing(foo, bar)
just
do_some_thing(foo, bar)
There you have it. OO is an unsound approach which survived so long mainly due to the perceived real world "analogy" and because the Object-Verb-Predicate syntax simplifies code completion.
I like the simplicity of this. If the app is of appreciable size, do_some_thing depends on databases, webservers, external processes, filesystems and configuration. How do you test/debug/explore its functionality without setting all of that up?
I would actually say these all make good "objects", i.e. isolated pieces. On the other hand, no runtime polymorphism is needed. To avoid OOP at a syntactic level, what do you think of the following?
- For tests at a smaller level, decompose the application such that most parts are easily testable in isolation (without external "hard" dependencies).
- For mid-sized tests with external dependencies but mostly unidirectional dataflow, setup a global virtual table with all the mock methods (and instances) that are needed. Alternatively, traditional linking methods.
- For larger "integration" tests there is no substitute to testing the real thing. At some scale and level of interactivity with the database, you just have to talk to the true filesystem, the true database etc. You can still setup a test instance for most cases where there is no interaction with external services.
I believe OO is good for exactly two things: abstract data containers and state machines.
In the former, access hiding cleans up the API and prevents unsafe usages of the container. In the latter, OO enforces a protocol to keep the state machine sealed off and only aware of key inputs and outputs.
And that's it. I've found nothing else. Data itself is better off when strategized to fit in a database, whether off-the-shelf or a custom-tuned, in-memory design. The state machines may need to query a part or all of the database, as well, so their ability to restrict scope only goes so far.
You nailed why I think many object oriented designs fall flat. People presuppose both that the objects within their domain encompass all objects (just students and courses) as well as that the objects within a domain will not change.
When #1 is missed I usually see that theres a design that doesn't mimic its domain and thus lose the ability for developers and users to have clear, concise communication. At that point OO is a disservice.
When #2 is missed you end up with IFilteredCourseAdapterProcessor as people attempt to bolt on components to solve future needs.
The addition of the "Registrar" to the domain immediately demonstrates how the naive interpretation is missing core components and I bet the users and devs fundamentally aren't speaking the same language.
This, imo, leads to conversations like, "Of course so and so approves all the registrations! Otherwise it would be madness!"
I've come to avoid OO and use only freestanding functions where possible mostly because of this problem. So often it ends up in a syntactic distinction that is absolutely meaningless otherwise.
I use some OO to make abstract datatypes in languages that are inherently OO, but I think the explicit virtual table approach in C or the type classes approach in Haskell are much cleaner.
Lately I had to make a REST API which is basically a distributed Object-oriented interface. I think I managed to get it done with compromises, but I'm not happy. Another idea would be to make a procedural interface first and then make a REST API on top if needed. But I have some doubts it can work out practically.
An API is there because someone in the other end wants to do something. If you don't design it but just slap REST on top of your data, then you're not doing that person a favour.
Well you can still "design REST" on top, right? But it could mean some duplicated efforts.
Actually, after having tried a few times, I'm pretty sure I don't want to put REST ideas at the center of my architecture. You just happen to need a network transport, and not even in all cases (debugging for example). And an existing model is never going to be able to represent all the concepts of your application domain. This means that you need to build your own representations.
In theory there is a point of using a standardized object protocol which can represent some CRUD use cases. But from my limited experience I think it breaks pretty quickly, to the point where I can use only GET and POST.
Using protocol layers of increasing specificity may make it easier to use existing tooling with data exchanges. For example, many APIs use HTTP statuscodes as a more coarse-grained version of the return codes in the body (in an ad-hoc format). Also caching is often brought up as an example where you want to buy in to a standardized protocol.
But some APIs don't buy in, like Facebook, which reportedly returns 200 OK always. It seems like a lot of best-effort work with little returns to me as well. But I don't know - I'm not a professional business software guy.
This is actually a part of understanding the specific domain. How do the educational practitioners think about the problem for application your working on? Is the application student or course centric? This is big part of DDD, and involves working domain experts and getting inside each others heads.
You could defiantly model it as the Register method on the course. But this example shows how you can avoid people messing around with internals once you've modeled it. There is only one way of registering for a course.
I wonder how to even formulate the question to the practitioners -- it seems like an artificial choice imposed by the computer formalism (single-dispatch OO).
In other words, I don't see how it is a domain question, and it seems likely that the domain practitioners just think "there are courses, and there are students, and students can be registered for courses".
My suspicion is that DDD would basically be better without the focus on single dispatch OO.
See my comment below. The language of the business will almost always guide the design in the right direction. There are rare cases where the business is unaware of a more "essential truth." This isn't about OO it's about faithfully capturing the model of the domain which is all DDD is.
If I say "students can be registered for courses in the school" then you might think the OO model should be
school.register(student, course)
which does actually seem reasonable to me. Here the school object would basically be the system's logic as a whole, and both the course and the student could just be IDs.
I really think single dispatch OO is a huge distraction and I haven't seen any strong arguments for its use as a general paradigm.
Nothing about DDD says you can't use multi-dispatch. If this how you think about the problem. Then this is your solution if your language is capable of expressing it.
Yeah, I'm quite a big fan of Eric Evans and DDD. Especially the more high level parts about ubiquitous language, bounded contexts, anti-corruption layers, and many of the other patterns. Yet I think that the focus on single-dispatch OO kind of dates the book and makes it less general and beautiful, much like how the GoF's "Design Patterns" book is more banal than Christopher Alexander's work on pattern languages because of the overemphasis on specific implementations of single dispatch OO patterns (the notorious visitor pattern, for example).
If the pairings of courses and students are registered in the school object, then that's basically OO implementation of a many to many relationship of students and courses.
That's why I like Django. You just tell it that you want a many-to-many between students and courses and it does they for you, symmetrically, without forcing you to introduce a class such as "school".
All your trying to do is match their language, so its easier to develop against a description of a use case. Technically either way allows you do what you want to do.
I know there are ways to solve the problem; but I believe these are accidental complexities, not essential complexities. That is, these design solutions complect the problem, sacrificing simplicity for a dubious principle.
Actually I think this is a major advantage. They are not complexities, they are putting down into code how you think about your application in your head.
If your design is some data, which you can mutate however you want to get the job done, you don't really have model of the application.
Your business rules can become inconsistent because different developers are implementing different but similar methods in your business logic layer all applying slightly different rules. If the entities themselves enforce these rules, it become a lot less likely.
If you are subscribing to the DDD snake oil, you usually weasel out of this decision by claiming that it belongs to the student in one Bounded Contextâ„¢ and to the course in another.
It's always a good idea in these situations to appeal to real life. The actual business will point the way of the business simulation. In the real world we don't ask students to register for a course. Instead students ask the Registrar to register for a course. When the Registrar makes her decision she considers far more than just the internal state of the Student; she considers (1) does the course have any available seats? (2) has the Student met all the course pre-requisites and, most importantly, (3) has the student paid his tuition and is he even a valid member of the university community.
What the Student does have is a history and a context -- that is, a state -- that must be considered when registering for courses. The student may also have preferences -- courses he wants to register for.
The language of the business should guide these decisions always. A student submits a request tfor a course and it is the office of that accepts or denies this request.