Go architectures · 3 MIN READ

SOA and RPC: stable service contracts

Compare service-oriented integration with smaller independently owned services.

Compare service-oriented integration with smaller independently owned services. Service-oriented architecture organizes reusable business capabilities behind contracts, often across existing enterprise systems. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Architecture · A MAP OF THE IDEA

  1. Consumer sends a defined request
  2. Service enforces its contract
  3. Consumer interprets a defined result

Useful when

  • Existing systems need explicit integration boundaries
  • Capabilities are reused across several applications

Watch out

  • Shared contracts need compatibility management
  • Centralized orchestration can become a bottleneck

Implementation & operation cost: High

Fit depends on the task

Small tool or CLI1/5
Domain-rich application3/5
Independent distributed services4/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

Service-oriented architecture organizes reusable business capabilities behind contracts, often across existing enterprise systems. Neither SOA nor RPC requires a particular protocol. A contract needs explicit units, failure semantics and version rules regardless of JSON or generated stubs.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"errors"
	"fmt"
)

type QuoteRequest struct{ WeightGrams int }
type QuoteResponse struct{ PriceCents int }

func Quote(req QuoteRequest) (QuoteResponse, error) {
	if req.WeightGrams <= 0 {
		return QuoteResponse{}, errors.New("weight required")
	}
	return QuoteResponse{PriceCents: 500 + req.WeightGrams/10}, nil
}

func main() {
	result, err := Quote(QuoteRequest{WeightGrams: 200})
	fmt.Println(result.PriceCents, err)
}

// Output:
// 520 <nil>
Read the code

The request and response encode units in field names. The example uses a local function so the contract is visible without transport code. An RPC adapter would still need to distinguish validation errors, deadline expiry and unavailable dependencies.

03

Tradeoffs and the simpler alternative

Broad shared services can reduce integration duplication but become release bottlenecks. Avoid a universal service that owns every domain. Microservices emphasize independent capability ownership; SOA is a broader service integration approach, so real systems can share ideas from both.

04

Design an API contract for independent clients

An RPC method should express a stable business operation rather than expose a database row shape. Define identifiers, validation, cancellation and error categories, along with message evolution rules. Adding an optional field may be compatible while changing its meaning is not. Keep generated transport types at the adapter boundary if their evolution should not dictate the domain model. An interface in Go is a local language contract; it does not automatically provide a versioned remote protocol.

05

Make timeout ambiguity explicit

If a client times out on Reserve, it may not know whether a reservation exists. A stable operation ID allows retry or status lookup without repeating the effect. Set one deadline and distinguish validation failure, denied access, missing resource and transient service failure. Avoid returning unbounded result lists; pagination and resource budgets belong in the contract. Streaming APIs also need backpressure and a shutdown policy.

06

Verify mixed-version deployment

Run contract fixtures against old and new request shapes. Test that a server accepting a new field still handles older callers, and that clients tolerate allowed unknown fields according to the protocol. Record latency and error rate per operation without logging credentials or whole sensitive payloads. RPC or service-oriented integration can centralize a genuine shared capability, but a synchronous dependency on many services can become a distributed call chain with poor availability.

TAKE THIS WITH YOU

Defines units, outcomes and compatibility at a boundary. Broad shared services can reduce integration duplication but become release bottlenecks. Avoid a universal service that owns every domain. Microservices emphasize independent capability ownership; SOA is a broader service integration approach, so real systems can share ideas from both.

Check your understanding

Which statement best describes SOA and RPC: stable service contracts?

Go to the source

Progress stays in this browser. No account needed.