Use a Service any time one set of Pods needs to talk to another, or anything needs a stable address into a workload. It is the standard way to do internal east-west routing and basic L4 load balancing inside a cluster, and it costs nothing extra.
The main limitation is that a plain Service operates at L4 (TCP and UDP). It does not understand HTTP, so it cannot route by hostname or path, terminate TLS, or do retries based on status codes. For external HTTP traffic you usually put an Ingress or a Gateway API resource in front, and that Ingress controller still routes to a Service.
Load balancing is also per-connection at the kube-proxy layer, not request-aware. For long-lived gRPC or HTTP/2 connections, all requests can pin to one Pod because the connection never reopens. Teams that need request-level balancing, mTLS, or fine traffic shaping reach for a service mesh such as Istio or Linkerd, which still builds on top of Services. Creating a LoadBalancer Service per app also gets expensive in cloud bills, which is another reason a single shared Ingress is common.