Splitting an application into services creates new places to put code. It does not automatically make the responsibilities clearer.
Before choosing how many things to deploy, ask which part of the system owns each decision.
Give each rule a home
Consider a booking application. A booking can be cancelled until a deadline. That rule belongs somewhere specific, where every path through the application can use it.
If the web interface and the background worker each implement their own version, the system has two answers to the same question.
Make the boundary explicit
One function can express the question the rest of the application needs to ask:
type Booking = {
status: 'confirmed' | 'cancelled';
cancellationDeadline: Date;
};
function canCancel(booking: Booking, now: Date): boolean {
return booking.status === 'confirmed'
&& now < booking.cancellationDeadline;
}
Passing the current time as an argument makes the rule easier to examine. This function only checks a rule; changing the booking still needs to handle concurrent requests and persistence.
Separate when you have a reason
A module can become a separately deployed service when there is a concrete need: different scaling, independent ownership, or an operational constraint.
Until then, a clear boundary inside one application is still useful. Write down what it owns and what it promises to the rest of the system.