The Question Most Projects Skip
By the time most tools have a directory, someone has already decided they will be a service. It is rarely a decision anyone remembers making — it arrives with the framework, the template, the tutorial, the deployment target. And because everything downstream assumes it, the assumption never gets tested.
The question worth asking out loud is narrow: does anything need an answer from this at a moment nobody chose in advance? If yes, it needs to be available, and availability means a service. If every single invocation traces back to a person deciding to run it, then availability was never the requirement, and a port is a cost with nothing on the other side of it.
What a Port Actually Costs
The build cost is trivial and it is not the point. The costs that matter start after it exists and never stop:
- A lifecycle to maintain — it has to start on boot, restart on crash, and survive an upgrade of the language runtime under it.
- A liveness question — someone eventually has to be able to ask whether it is up, which means a health endpoint, which means something that reads it.
- An address to defend — a reserved number, a collision to avoid, a decision about who can reach it.
- An implied contract — this is the expensive one. Anything reachable will eventually be depended on, and the second caller arrives without asking permission.
That last cost is why the refusal is stated as a rule rather than a preference. A batch tool that becomes a service has not gained a feature; it has acquired an unbounded set of future callers whose expectations it did not agree to.
The Honest Counter-Case
This is not an argument that services are bad, and a lesson that pretended otherwise would be useless. Real triggers for a service are easy to state: several independent clients need the same answer; latency matters enough that process startup is unacceptable; state has to be shared and coordinated across callers; something outside your control initiates the work. Any one of those and the cost is earned. Beacon has none of them. It has exactly one caller, startup time is irrelevant against a render measured in minutes, its state is a directory, and every run begins with a person.
What makes this a transferable lesson is the order of reasoning. Do not start from "what shape should this be?" — start from "who initiates work, and when?" The shape falls out of the answer, and it will sometimes be a service. The failure mode is skipping the question, because the default answer is always yes and the cost is always permanent.