2

I have two microservices, say "students" and "courses", they represent separate entities that are managed separately by different teams.

A given student can be enrolled in multiple courses. In a monolythic app, I'd just use a students-to-courses table to do the mapping and would be done with it. What's the recommended way to model this relationship if the two entities are managed in separate microservices? Ideally, I'd like to be able to query the students service to get a list of courses a given student is attending, and query the courses service to get a list of the students attending that course.

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.

Apologies for the confusion but I'm fairly new to this.

Thanks

3
  • 1
    Unless your courses are individual tutoring session, this is not a many-to-one relationship, it is a many-to-many.
    – Flater
    CommentedDec 15, 2023 at 3:23
  • You haven't stated what the responsibility of the two services. How are they going to be used? What information does each hold, what queries are supported, what notifications provided?
    – Erik Eidt
    CommentedDec 15, 2023 at 16:16
  • @Flater you're of corse right, I was thinking only of the student -> courses direction. Fixing.
    – persson
    CommentedDec 15, 2023 at 22:49

4 Answers 4

4

In what sane world does the administration of two simple lists - students and courses - get managed by separate teams?

Where you have an interface between two databases, you typically have to accept that there will be transactional inconsistencies, a limitation on live reporting, and (at least occasionally) different times of availability.

This typically happens when two completely different corporate organisations are speaking to each other. There is enormous complexity to the resolution.

Why then would you design a records system for an organisation such as a college, with these inefficiencies already existing internally? These are the kinds of inefficiencies a competent person would seek to design out of the system.

A lot of people seen to think that "monoliths" have only disadvantages, without any clear idea of why monoliths were ever made. In fact, monoliths have important properties that don't exist otherwise - like consistency, and simplicity under conventional circumstances.

To make the proper granularity clear, a "service" in a correctly designed application should correspond roughly to a database engine (i.e. an entire RDBMS instance).

Unless there are extraordinary legacy technical constraints, it should not remotely approach the granularity of one service per table, which is utter Rube-Goldberg territory.

    3

    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.

      3

      Microservices do not impose the lowest possible granularity

      Highly granular application decomposition with microservices per aggregate roots / entities is possible. However, as explained by Chris Richardson, the pioneer of microservices, there are other decomposition strategies:

      - By business capability
      - By subdomain
      - By team
      - By self-contained service

      You could therefore split students and courses in separate self-contained microservices, but you could also keep them together: they could belong to same business capability, and perhaps even to the same subdomain.

      Keeping microservices self-contained

      If you go by aggregate root, both microservices should be self-contained and remain independently deployable (i.e. a newer version of course microservice could be deployed and should not affect running student service).

      One possibility to achieve this is to keep the minimum possible (but necessary) knowledge in each microservices.

      • Microservice 1: manages students and is able to provide the list of courses that the student is enrolled in. The course list would be very minimalistic with only the identifier of the course (details to be searched on the course service) .
      • Microservice 2: manages courses and is able to provide the list of students who enrolled in that course. The student list would be very minimalistic with only the identifier of the student (details to be searched on the student service)

      If you use a NoSqldocument-oriented db, like MongoDb, this would be a natural choice: the student document would contain a collection of course ids, and vice-versa. The fact that both are on different services, in different db, is completely irrelevant.

      In a RDBMS, you'd use proxy tables in each database that allow to refer to a remote object:

      enter image description here

      This decomposition would use the saga pattern or domain events, to enrol students in courses in a coordinated way. This could be offered by a separate enrolment service as suggested by J_H or directly by one of the service (for example course).

      3
      • StudentCourse and CourseStudent are mirror tables. But which is the master? There's a cycle of dependencies without any indication of where an algorithm to modify data would or should naturally begin. In a transactional system, the question is lower-stakes, because the modifications would be atomic, but this algorithm has to cross the boundaries of a transaction manager, so which is master and slave is potentially crucial. And if the modification can begin on either side concurrently, with no designated master, you need versioning and audit trails, and conflict-resolution mechanisms.
        – Steve
        CommentedDec 18, 2023 at 10:40
      • @Steve no they are not mirror tables. Miror table is to reflect the same information. Both are participation tables with a reference to an entity managed in another system. Each service could need to know, for example the course could need to know how many students registered and how many places are still free. Now the coordination could be performed by a master (e.g. course registers the students and publishes domain events used by student to update its participation). But you could also allow for completely decentralized choreographie, where any update in one update the other if necessary.CommentedDec 18, 2023 at 12:56
      • And yiu could still offer an enrollment service that would orchestrate this.CommentedDec 18, 2023 at 12:57
      2

      Create an "enrollments" service that associates the two.

      query the students service to get a list of courses a given student is attending

      That is certainly possible, the students service can transparently query enrollments for you and pass along the result. But it may be simpler for client to just query enrollments directly. It will need to support both student->class and class->student queries.

      3
      • 1
        I disagree about having the students service query the enrollments service purely as a matter of creating a merged result. This creates unnecessary coupling between two services. That's not to say that services can't ever talk to each other, e.g. if enrollment needs to check that the given student ID actually exists - but that's a functional reason to connect them. Querying isn't that. If there is a need for a single API call which contains both student and enrollment data, then this is better implemented using a BFF. This way, the individual microservices don't blend their responsibilities
        – Flater
        CommentedDec 15, 2023 at 3:55
      • If I create an enrollment service, and a client wants to know all courses for a student (for example), it will get back a list of ids; it will then have to go request them to the courses microservice. For the courses microservice, this potentially means a lot of requests for single items; should the service also allow to request multiple ids at once? What would be the request format for that?
        – persson
        CommentedDec 30, 2023 at 23:19
      • Nevermind, I found stackoverflow.com/questions/4541338/… and other pages that discuss that.
        – persson
        CommentedDec 31, 2023 at 12:05

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.