J_H touched on the right answer and posted earlier than me, but I feel like this requires more elaboration.
A given student can be enrolled in multiple courses.
Unless your courses are individual tutoring session, this is not a many-to-one relationship, it is a many-to-many. I'm pointing this out because using the wrong name will give you false results when you ask/search other resources.
In a monolithic app, I'd just use a students-to-courses table to do the mapping and would be done with it.
Option 1
Cross tables like these sometimes live in secret. The app only really deals with students and courses, and while you can manage one via the other, you never really treat their cross table as a meaningful domain model in and of itself.
Option 2
In other cases, cross tables exist explicitly. They are represented by a domain model of their own, because they represent an actual concept. In the context of students and courses, this would usually be called an "enrollment".
When you think about it, an explicitly existing cross table could just as easily be argued to just be a table with two FK properties in it. That's effectively saying the same thing, really.
When dealing with microservices, you are strongly suggested to favor option 2. The reason for that is a series of decisions related to microservice good practices:
- Microservices independently define their own bounded context, therefore we struggle to insert the enrollments into either the students or the courses service - it's just not part of its remit.
- Registering the enrollment both in the students and the courses service means that you open yourself up to two sources of truth contradicting each other. At best it's more effort, at worst you lose your data integrity.
- Merging two microservices because they have a downstream relation, when applied as a general rule, effectively brings you back to a monolithic architecture.
The only remaining solution there is to define enrollments as their own bounded context, therefore creating a single source of truth that is not foisted into a different bounded context and without resorting to monolithic architectures.
Perhaps a solution could be to join the services under a single service? Which kind of REST API would such a single service expose? Consider that there would also be external consumers that would need to update either the students or the courses separately.
At a base level, three separate microservices means three separate APIs. Your interaction with the enrollment API will hinge on referring to existing students and courses. If they don't already exist, then you can't enroll a non-existing student to a course (or vice versa).
Maybe your frontend has a need for a "student with their courses" model or vice versa. This could be because you're printing a list of all student names in a course, or a report card for a student, or ... pick your example. You have two choices here.
Either you just let your frontend call the microservices directly and stitch the information together by itself; or you create a Backend For Frontend (BFF) which stitches the information together and returns the merged result to the frontend.
Whether you need this additional layer or not is highly contextual. I would generally not bother with it unless you have a good reason to not just call the microservices directly.