2

If for example we have a user, project and a task service where a task is assigned to a single user and user can be part of multiple projects - how can one enforce a rule so that one cannot assign a task to a user that is not part of the project?

For example, a POST request comes in to /tasks/1/assign with this data:

{
  "task_id": 1,
  "assignee": 7
}

but the user with ID 7 does not belong to a project that task with ID 1 is a part of.

For request to succeed at some point in the request life-cycle, a check must be made to validate that a user is part of a project.

I see the following options:

  1. Synchronous call from task to user service to check if the user belongs to a project.
  2. Async data replication where user service emits UserAssignedToProject event that task service accepts, stores the relation in its SQL database and then checks on assign request.

What are other solutions to this problem?

5
  • Use a single database. Commented Jul 6, 2022 at 18:59
  • @ReinstateMonica imagine a more complex system with a lot more independent services that require this type of relationship. Sure, you can argue that if you need this type of relationship, especially for this example, then you should not separate them in different services/databases. But, let's imagine for the sake of discussion a more complex system.
    – kisibip183
    Commented Jul 6, 2022 at 19:09
  • 1
    Then tune the database so that it performs well enough and run it on fast hardware. There are very few situations where a relational database can't handle the load. If you're in one of those rare situations, you can hire a dozen engineers to manage a distributed data store. Commented Jul 6, 2022 at 20:04
  • Seems like a poor selection of microservices, similar to the CRUDy API problem. Rethink your services.
    – Peter K.
    Commented Jul 8, 2022 at 18:51
  • This question is a list question, which is off-topic. It's okay to ask for a solution to a problem, but asking for us to list other solutions is going to draw opinionated answers and render the question unresolvable (because there's always one more way to do it).
    – Flater
    Commented Apr 3, 2023 at 23:56

3 Answers 3

2

how can one enforce a rule so that one cannot assign a task to a user that is not part of the project?

The first question is not how to apply this rule, it's how to know that a user is or isn't part of a project.

We're in the field of information technology, which is all about data communication. In a way, humans have been optimizing data communication for millennia, it's called "having a conversation".

Tongue-in-cheek comment aside, there is value to drawing a real life analogy here. Consider what a person (with the same knowledge as your app) would need to say/ask to verify this information with others, because the solution will be the same.

I'll do it in the form of a script:

[CUSTOMER]: Hi there TaskService. I want you to assign task 1 to user 2.

[TASK SERVICE]: I know this task, because I am the task service! This task is about reticulating the splines for project ABC. However, I do not know this customer. Hey UserService, who is user 2?

[USER SERVICE]: It's Bob Bobson. He works for project XYZ.

[TASK SERVICE]: Thanks! I'm sorry customer but I cannot assign task 1 to user 2.

Or alternatively:

[...]

[USER SERVICE]: It's Carl Carlson. He works for project ABC.

[TASK SERVICE]: Thanks! Okay, Carl is allowed to be assigned this task. Let me just write that down... Done! Customer, I have done as you asked. Have a nice day!

I'm being a bit silly with it here for the sake of simple examples, but I hope this makes sense. Microservices are like people who each have their own expertise. In order for any person to perform any task that hinges on knowledge that spans across several people, these people will need to confer with one another in order to make sure that the correct thing is done.

Since the core of the action here is creating a task assignment, the task service is in the driving seat here. It has free access to the data that it manages (i.e. task data), but it has to ask the other services about the data that they manage.

The task service uses that information to formulate a response to the core action. In the case of Bob, it formulates a refusal to do so (validation error). In the case of Carl, it does what it was asked to do.

This is the optimal arrangement that minimizes the effort/interaction while at the same time maximizing each microservice's individual domain; and therefore it is the better solution.

8
  • Does this not maximize the chance of failure? Especially if this approach is used with more services, if any of the calls fails, then that causes the entire request to fail. And even if the chance of any given networrk request failing is 0.0001%, it starts to grow rapidly as more services are involved. Commented Apr 4, 2023 at 3:17
  • @user1937198: Retry policies are a thing. This is also a matter of picking your poison. Microservices come with a certain overhead cost and certain benefits. So do monoliths - different ones of course. Secondly, you would need to have 10,000 network calls in a single request before your 0.0001% would even approach a 1% chance of failure, and that's assuming that any singular request failure cannot be retried and has no fallback ability. That's just not a realistic amount of network dependencies to be calling. Show me a company that has 10,000 different microservices (not instances!)
    – Flater
    Commented Apr 4, 2023 at 3:34
  • @user1937198: To put it differently, if you balk at the idea of doing a networked call to a different service and would much rather keep it within the same runtime, then microservices are not the right architecture for you. The posted question however is firmly rooted in a microservice architecture, and therefore so is my answer.
    – Flater
    Commented Apr 4, 2023 at 3:38
  • its not that I balk at the idea of a network call, so much as a network call in the critical path. Asynchronous techniques allow avoiding the risk of failure in the critical path. And 1% is an unacceptably high chance of failure in most systems. 0.1%-0.01% is a more common requirement. Commented Apr 4, 2023 at 11:00
  • @user1937198: Asynchrony does not affect failure rates of requests. Obvious async-related bugs aside (which, if we counted them, would count against asynchrony), a failure would remain a failure regardless of it being handled synchronously or asynchronously. Even with your shifted goalposts of what constitutes a "common requirement", your 0.1%-0.01% example still requires between 100 and 1,000 network calls needed to resolve a single request. It's still nowhere near a realistic ballpark of how many extra network calls a microservice architecture adds into the mix.
    – Flater
    Commented Apr 4, 2023 at 23:30
1

In general, you cannot.

Even with a synchronous call, there is a race condition where the user might exist in the project when you check, but be removed before you can insert.

Distributed transactions can help with this, but are heavyweight and tend to mitigate the benefits of microservices in the first place.

You could do the check on a GET to verify the relation, but then you’re coupling that operation (and it’s infeasible for things like “get me all users in this project”).

Mostly, you have some UI accommodate the possibility that a user is deleted/archived by the time it gets their details, and usually have the async option you mentioned or some garbage collector kill of old relations.

0

How about an assignment service ? The service that does the assignment, and keeps the mappings at one place, as well as updates them atomically :

| user | project | task |
-------------------------
| u1   | p1      | t1   |
| u2   | p1      | t2   |

The user, task and project services can continue to manage their respective entities, without relating them with each other.

Although, sooner or later you will have to accept the fact that this less than ideal, partitioned consistency is what you get for a distributed design. There are pros & cons of micro-services, that come with the design, no way around those.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.