Repository: goadesign/goa
Pull request #3721 (2025-06-07 05:00:37)

refactor(codegen): update code generation to use non-global root
# refactor(codegen): eliminate global dependencies and improve code generation architecture

This PR refactors the code generation architecture to remove global dependencies and improve maintainability across the codebase. The changes span three main areas that were causing confusion and making the code harder to maintain.

## Changes

### Remove Global Root Expression Dependencies

This commit modifies the code generation logic to consistently utilize non-global root expressions across various files. Changes include updating function signatures and internal calls to pass the root expression as a parameter, ensuring that the generated code correctly references the API structure. This refactor removes the need for code generation algorithms to refer to the global root expression variable.

### Restructure ServicesData Architecture  

The most significant change eliminates the confusing ServicesData global dependencies in both transport and non-transport code generation algorithms. Previously, we had a confusing pattern where ServicesData contained a ServiceData field, making it unclear which layer you were working with.

Key improvements:
- Embed service.ServicesData directly in transport packages to eliminate confusing field names
- Rename transport service maps to GRPCServices/HTTPServices for clarity
- Add constructor functions to reduce boilerplate initialization
- Update variable names from serviceData to services for semantic accuracy
- Establish consistent patterns across service, HTTP, and gRPC codegen packages

This creates clear separation between service layer and transport layer data structures.

### Streamline Service File Generation

- Consolidate file appending in Service function for improved readability
- Simplify section template creation in EndpointFile and ViewsFile functions
- Introduce IsAliased and ServiceTypeRef fields in ServiceData for better type resolution
- Update request initialization template to utilize new fields for aliased types

## Impact

These changes enhance the clarity and efficiency of the code generation process while ensuring better maintainability and consistency across service files. The refactor maintains full backward compatibility for generated code - no changes are required for existing Goa applications.

The elimination of global state dependencies makes the codebase more testable and modular, while the improved naming conventions make it much clearer which layer of the system you're working with at any given time.


Patch:
diff --git a/codegen/example/example_client.go b/codegen/example/example_client.go
index 5e6bfa64..7b71b023 100644
--- a/codegen/example/example_client.go
+++ b/codegen/example/example_client.go
@@ -24,7 +24,7 @@ func CLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 // exampleCLIMain returns an example client tool main implementation for the
 // given server expression.
 func exampleCLIMain(_ string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
-	svrdata := Servers.Get(svr)
+	svrdata := Servers.Get(svr, root)
 
 	path := filepath.Join("cmd", svrdata.Dir+"-cli", "main.go")
 	if _, err := os.Stat(path); !os.IsNotExist(err) {
diff --git a/codegen/example/example_server.go b/codegen/example/example_server.go
index 35576abf..091b3adb 100644
--- a/codegen/example/example_server.go
+++ b/codegen/example/example_server.go
@@ -13,10 +13,10 @@ import (
 
 // ServerFiles returns an example server main implementation for every server
 // expression in the service design.
-func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ServerFiles(genpkg string, root *expr.RootExpr, services *service.ServicesData) []*codegen.File {
 	var fw []*codegen.File
 	for _, svr := range root.API.Servers {
-		if m := exampleSvrMain(genpkg, root, svr); m != nil {
+		if m := exampleSvrMain(genpkg, root, svr, services); m != nil {
 			fw = append(fw, m)
 		}
 	}
@@ -25,8 +25,8 @@ func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 
 // exampleSvrMain returns the default main function for the given server
 // expression.
-func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
-	svrdata := Servers.Get(svr)
+func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, services *service.ServicesData) *codegen.File {
+	svrdata := Servers.Get(svr, root)
 	mainPath := filepath.Join("cmd", svrdata.Dir, "main.go")
 	if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
 		return nil // file already exists, skip it.
@@ -52,7 +52,7 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
 	scope := codegen.NewNameScope()
 	hasInterceptors := false
 	for i, svc := range svr.Services {
-		sd := service.Services.Get(svc)
+		sd := services.Get(svc)
 		svcData[i] = sd
 		specs = append(specs, &codegen.ImportSpec{
 			Path: path.Join(genpkg, sd.PathName),
diff --git a/codegen/example/server_data.go b/codegen/example/server_data.go
index 35cff571..c80bde45 100644
--- a/codegen/example/server_data.go
+++ b/codegen/example/server_data.go
@@ -109,11 +109,11 @@ const (
 
 // Get returns the server data for the given server expression. It builds the
 // server data if the server name does not exist in the map.
-func (d ServersData) Get(svr *expr.ServerExpr) *Data {
+func (d ServersData) Get(svr *expr.ServerExpr, root *expr.RootExpr) *Data {
 	if data, ok := d[svr.Name]; ok {
 		return data
 	}
-	sd := buildServerData(svr)
+	sd := buildServerData(svr, root)
 	d[svr.Name] = sd
 	return sd
 }
@@ -170,14 +170,12 @@ func (h *HostData) DefaultURL(transport Transport) string {
 }
 
 // buildServerData builds the server data for the given server expression.
-func buildServerData(svr *expr.ServerExpr) *Data {
+func buildServerData(svr *expr.ServerExpr, root *expr.RootExpr) *Data {
 	var (
 		hosts []*HostData
 	)
-	{
-		for _, h := range svr.Hosts {
-			hosts = append(hosts, buildHostData(h))
-		}
+	for _, h := range svr.Hosts {
+		hosts = append(hosts, buildHostData(h))
 	}
 
 	var (
@@ -185,16 +183,14 @@ func buildServerData(svr *expr.ServerExpr) *Data {
 
 		foundVars = make(map[string]struct{})
 	)
-	{
-		// collect all the URL variables defined in host expressions
-		for _, h := range hosts {
-			for _, v := range h.Variables {
-				if _, ok := foundVars[v.Name]; ok {
-					continue
-				}
-				variables = append(variables, v)
-				foundVars[v.Name] = struct{}{}
+	// collect all the URL variables defined in host expressions
+	for _, h := range hosts {
+		for _, v := range h.Variables {
+			if _, ok := foundVars[v.Name]; ok {
+				continue
 			}
+			variables = append(variables, v)
+			foundVars[v.Name] = struct{}{}
 		}
 	}
 
@@ -205,23 +201,21 @@ func buildServerData(svr *expr.ServerExpr) *Data {
 
 		foundTrans = make(map[Transport]struct{})
 	)
-	{
-		for _, svc := range svr.Services {
-			_, seenHTTP := foundTrans[TransportHTTP]
-			_, seenGRPC := foundTrans[TransportGRPC]
-			if expr.Root.API.HTTP.Service(svc) != nil {
-				httpServices = append(httpServices, svc)
-				if !seenHTTP {
-					transports = append(transports, newHTTPTransport())
-					foundTrans[TransportHTTP] = struct{}{}
-				}
+	for _, svc := range svr.Services {
+		_, seenHTTP := foundTrans[TransportHTTP]
+		_, seenGRPC := foundTrans[TransportGRPC]
+		if root.API.HTTP.Service(svc) != nil {
+			httpServices = append(httpServices, svc)
+			if !seenHTTP {
+				transports = append(transports, newHTTPTransport())
+				foundTrans[TransportHTTP] = struct{}{}
 			}
-			if expr.Root.API.GRPC.Service(svc) != nil {
-				grpcServices = append(grpcServices, svc)
-				if !seenGRPC {
-					transports = append(transports, newGRPCTransport())
-					foundTrans[TransportGRPC] = struct{}{}
-				}
+		}
+		if root.API.GRPC.Service(svc) != nil {
+			grpcServices = append(grpcServices, svc)
+			if !seenGRPC {
+				transports = append(transports, newGRPCTransport())
+				foundTrans[TransportGRPC] = struct{}{}
 			}
 		}
 	}
@@ -247,79 +241,68 @@ func buildServerData(svr *expr.ServerExpr) *Data {
 
 // buildHostData builds the host data for the given host expression.
 func buildHostData(host *expr.HostExpr) *HostData {
-	var (
-		uris []*URIData
-	)
-	{
-		uris = make([]*URIData, len(host.URIs))
-		for i, uv := range host.URIs {
-			var (
-				t      *TransportData
-				scheme string
-				port   string
+	uris := make([]*URIData, len(host.URIs))
+	for i, uv := range host.URIs {
+		var (
+			t      *TransportData
+			scheme string
+			port   string
 
-				ustr = string(uv)
-			)
-			{
-				// Did not use url package to find scheme because the url may
-				// contain params (i.e. http://{version}.example.com) which needs
-				// substition for url.Parse to succeed. Also URIs in host must have
-				// a scheme otherwise validations would have failed.
-				switch {
-				case strings.HasPrefix(ustr, "https"):
-					scheme = "https"
-					port = "443"
-					t = newHTTPTransport()
-				case strings.HasPrefix(ustr, "http"):
-					scheme = "http"
-					port = "80"
-					t = newHTTPTransport()
-				case strings.HasPrefix(ustr, "grpcs"):
-					scheme = "grpcs"
-					port = "8443"
-					t = newGRPCTransport()
-				case strings.HasPrefix(ustr, "grpc"):
-					scheme = "grpc"
-					port = "8080"
-					t = newGRPCTransport()
+			ustr = string(uv)
+		)
+		// Did not use url package to find scheme because the url may
+		// contain params (i.e. http://{version}.example.com) which needs
+		// substition for url.Parse to succeed. Also URIs in host must have
+		// a scheme otherwise validations would have failed.
+		switch {
+		case strings.HasPrefix(ustr, "https"):
+			scheme = "https"
+			port = "443"
+			t = newHTTPTransport()
+		case strings.HasPrefix(ustr, "http"):
+			scheme = "http"
+			port = "80"
+			t = newHTTPTransport()
+		case strings.HasPrefix(ustr, "grpcs"):
+			scheme = "grpcs"
+			port = "8443"
+			t = newGRPCTransport()
+		case strings.HasPrefix(ustr, "grpc"):
+			scheme = "grpc"
+			port = "8080"
+			t = newGRPCTransport()
 
-					// No need for default case here because we only support the above
-					// possibilites for the scheme. Invalid scheme would have failed
-					// validations in the first place.
-				}
-			}
-			uris[i] = &URIData{
-				Scheme:    scheme,
-				URL:       ustr,
-				Port:      port,
-				Transport: t,
-			}
+			// No need for default case here because we only support the above
+			// possibilites for the scheme. Invalid scheme would have failed
+			// validations in the first place.
+		}
+		uris[i] = &URIData{
+			Scheme:    scheme,
+			URL:       ustr,
+			Port:      port,
+			Transport: t,
 		}
 	}
 
-	var (
-		variables []*VariableData
-	)
-	{
-		vars := expr.AsObject(host.Variables.Type)
-		if len(*vars) > 0 {
-			variables = make([]*VariableData, len(*vars))
-			for i, v := range *vars {
-				def := v.Attribute.DefaultValue
-				var values []string
-				if def == nil {
-					def = v.Attribute.Validation.Values[0]
-					// DSL ensures v.Attribute has either a
-					// default value or an enum validation
-					values = convertToString(v.Attribute.Validation.Values...)
-				}
-				variables[i] = &VariableData{
-					Name:         v.Name,
-					Description:  v.Attribute.Description,
-					VarName:      codegen.Goify(v.Name, false),
-					DefaultValue: convertToString(def)[0],
-					Values:       values,
-				}
+	vars := expr.AsObject(host.Variables.Type)
+	var variables []*VariableData
+	if len(*vars) > 0 {
+		variables = make([]*VariableData, len(*vars))
+		for i, v := range *vars {
+			def := v.Attribute.DefaultValue
+			var values []string
+			if def == nil {
+				def = v.Attribute.Validation.Values[0]
+				// DSL ensures v.Attribute has either a
+				// default value or an enum validation
+				values = convertToString(v.Attribute.Validation.Values...)
+			}
+			variables[i] = &VariableData{
+				Name:         v.Name,
+				Description:  v.Attribute.Description,
+				VarName:      codegen.Goify(v.Name, false),
+				DefaultValue: convertToString(def)[0],
+				Values:       values,
 			}
 		}
 	}
diff --git a/codegen/generator/example.go b/codegen/generator/example.go
index 82788014..fda92351 100644
--- a/codegen/generator/example.go
+++ b/codegen/generator/example.go
@@ -20,18 +20,21 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 			continue // could be a plugin root expression
 		}
 
+		// Create service data
+		services := service.NewServicesData(r)
+
 		// example service implementation
-		if fs := service.ExampleServiceFiles(genpkg, r); len(fs) != 0 {
+		if fs := service.ExampleServiceFiles(genpkg, r, services); len(fs) != 0 {
 			files = append(files, fs...)
 		}
 
 		// example interceptors implementation
-		if fs := service.ExampleInterceptorsFiles(genpkg, r); len(fs) != 0 {
+		if fs := service.ExampleInterceptorsFiles(genpkg, r, services); len(fs) != 0 {
 			files = append(files, fs...)
 		}
 
 		// server main
-		if fs := example.ServerFiles(genpkg, r); len(fs) != 0 {
+		if fs := example.ServerFiles(genpkg, r, services); len(fs) != 0 {
 			files = append(files, fs...)
 		}
 
@@ -42,20 +45,22 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 
 		// HTTP
 		if len(r.API.HTTP.Services) > 0 {
-			if fs := httpcodegen.ExampleServerFiles(genpkg, r); len(fs) != 0 {
+			httpServices := httpcodegen.NewServicesData(services)
+			if fs := httpcodegen.ExampleServerFiles(genpkg, httpServices); len(fs) != 0 {
 				files = append(files, fs...)
 			}
-			if fs := httpcodegen.ExampleCLIFiles(genpkg, r); len(fs) != 0 {
+			if fs := httpcodegen.ExampleCLIFiles(genpkg, httpServices); len(fs) != 0 {
 				files = append(files, fs...)
 			}
 		}
 
 		// GRPC
 		if len(r.API.GRPC.Services) > 0 {
-			if fs := grpccodegen.ExampleServerFiles(genpkg, r); len(fs) > 0 {
+			grpcServices := grpccodegen.NewServicesData(services)
+			if fs := grpccodegen.ExampleServerFiles(genpkg, grpcServices); len(fs) > 0 {
 				files = append(files, fs...)
 			}
-			if fs := grpccodegen.ExampleCLIFiles(genpkg, r); len(fs) > 0 {
+			if fs := grpccodegen.ExampleCLIFiles(genpkg, grpcServices); len(fs) > 0 {
 				files = append(files, fs...)
 			}
 		}
@@ -64,7 +69,7 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 		for _, f := range files {
 			if len(f.SectionTemplates) > 0 {
 				for _, s := range r.Services {
-					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s)
+					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, services.Get(s.Name))
 				}
 			}
 		}
diff --git a/codegen/generator/service.go b/codegen/generator/service.go
index 7f7169c4..3ecec131 100644
--- a/codegen/generator/service.go
+++ b/codegen/generator/service.go
@@ -18,21 +18,23 @@ func Service(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 		if !ok {
 			continue
 		}
+		// Create service data
+		services := service.NewServicesData(r)
+
 		for _, s := range r.Services {
 			// Make sure service is first so name scope is
 			// properly initialized.
-			files = append(files, service.Files(genpkg, s, userTypePkgs)...)
-			files = append(files, service.EndpointFile(genpkg, s))
-			files = append(files, service.ClientFile(genpkg, s))
-			if f := service.ViewsFile(genpkg, s); f != nil {
+			files = append(files, service.Files(genpkg, s, services, userTypePkgs)...)
+			files = append(files, service.EndpointFile(genpkg, s, services), service.ClientFile(genpkg, s, services))
+			if f := service.ViewsFile(genpkg, s, services); f != nil {
 				files = append(files, f)
 			}
 			for _, f := range files {
 				if len(f.SectionTemplates) > 0 {
-					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s)
+					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, services.Get(s.Name))
 				}
 			}
-			f, err := service.ConvertFile(r, s)
+			f, err := service.ConvertFile(r, s, services)
 			if err != nil {
 				return nil, err
 			}
diff --git a/codegen/generator/transport.go b/codegen/generator/transport.go
index f8979dd7..20f10065 100644
--- a/codegen/generator/transport.go
+++ b/codegen/generator/transport.go
@@ -19,26 +19,32 @@ func Transport(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 			continue // could be a plugin root expression
 		}
 
+		// Create service data
+		services := service.NewServicesData(r)
+
 		// HTTP
-		files = append(files, httpcodegen.ServerFiles(genpkg, r)...)
-		files = append(files, httpcodegen.ClientFiles(genpkg, r)...)
-		files = append(files, httpcodegen.ServerTypeFiles(genpkg, r)...)
-		files = append(files, httpcodegen.ClientTypeFiles(genpkg, r)...)
-		files = append(files, httpcodegen.PathFiles(r)...)
-		files = append(files, httpcodegen.ClientCLIFiles(genpkg, r)...)
+		httpServices := httpcodegen.NewServicesData(services)
+		files = append(files, httpcodegen.ServerFiles(genpkg, httpServices)...)
+		files = append(files, httpcodegen.ClientFiles(genpkg, httpServices)...)
+		files = append(files, httpcodegen.ServerTypeFiles(genpkg, httpServices)...)
+		files = append(files, httpcodegen.ClientTypeFiles(genpkg, httpServices)...)
+		files = append(files, httpcodegen.PathFiles(httpServices)...)
+		files = append(files, httpcodegen.ClientCLIFiles(genpkg, httpServices)...)
 
 		// GRPC
-		files = append(files, grpccodegen.ProtoFiles(genpkg, r)...)
-		files = append(files, grpccodegen.ServerFiles(genpkg, r)...)
-		files = append(files, grpccodegen.ClientFiles(genpkg, r)...)
-		files = append(files, grpccodegen.ServerTypeFiles(genpkg, r)...)
-		files = append(files, grpccodegen.ClientTypeFiles(genpkg, r)...)
-		files = append(files, grpccodegen.ClientCLIFiles(genpkg, r)...)
+		grpcServices := grpccodegen.NewServicesData(services)
+		files = append(files, grpccodegen.ProtoFiles(genpkg, grpcServices)...)
+		files = append(files, grpccodegen.ServerFiles(genpkg, grpcServices)...)
+		files = append(files, grpccodegen.ClientFiles(genpkg, grpcServices)...)
+		files = append(files, grpccodegen.ServerTypeFiles(genpkg, grpcServices)...)
+		files = append(files, grpccodegen.ClientTypeFiles(genpkg, grpcServices)...)
+		files = append(files, grpccodegen.ClientCLIFiles(genpkg, grpcServices)...)
 
+		// Add service data meta type imports
 		for _, f := range files {
 			if len(f.SectionTemplates) > 0 {
 				for _, s := range r.Services {
-					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s)
+					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, services.Get(s.Name))
 				}
 			}
 		}
diff --git a/codegen/service/client.go b/codegen/service/client.go
index 8cc61b2d..3c2ee2e9 100644
--- a/codegen/service/client.go
+++ b/codegen/service/client.go
@@ -13,8 +13,8 @@ const (
 )
 
 // ClientFile returns the client file for the given service.
-func ClientFile(_ string, service *expr.ServiceExpr) *codegen.File {
-	svc := Services.Get(service.Name)
+func ClientFile(_ string, service *expr.ServiceExpr, services *ServicesData) *codegen.File {
+	svc := services.Get(service.Name)
 	data := endpointData(svc)
 	path := filepath.Join(codegen.Gendir, svc.PathName, "client.go")
 	var (
diff --git a/codegen/service/convert.go b/codegen/service/convert.go
index 7d6c801a..1c2c9c97 100644
--- a/codegen/service/convert.go
+++ b/codegen/service/convert.go
@@ -132,10 +132,10 @@ func getExternalTypeInfo(external any) (string, string, error) {
 
 // ConvertFile returns the file containing the conversion and creation functions
 // if any.
-func ConvertFile(root *expr.RootExpr, service *expr.ServiceExpr) (*codegen.File, error) {
+func ConvertFile(root *expr.RootExpr, service *expr.ServiceExpr, services *ServicesData) (*codegen.File, error) {
 	// Filter conversion and creation functions that are relevant for this
 	// service
-	svc := Services.Get(service.Name)
+	svc := services.Get(service.Name)
 	var conversions, creations []*expr.TypeMap
 	for _, c := range root.Conversions {
 		for _, m := range service.Methods {
diff --git a/codegen/service/endpoint.go b/codegen/service/endpoint.go
index 8ca71567..32864a1c 100644
--- a/codegen/service/endpoint.go
+++ b/codegen/service/endpoint.go
@@ -63,8 +63,8 @@ const (
 )
 
 // EndpointFile returns the endpoint file for the given service.
-func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
-	svc := Services.Get(service.Name)
+func EndpointFile(genpkg string, service *expr.ServiceExpr, services *ServicesData) *codegen.File {
+	svc := services.Get(service.Name)
 	svcName := svc.PathName
 	path := filepath.Join(codegen.Gendir, svcName, "endpoints.go")
 	data := endpointData(svc)
@@ -115,8 +115,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
 			Name:   "endpoints-init",
 			Source: readTemplate("service_endpoints_init"),
 			Data:   data,
-		})
-		sections = append(sections, &codegen.SectionTemplate{
+		}, &codegen.SectionTemplate{
 			Name:   "endpoints-use",
 			Source: readTemplate("service_endpoints_use"),
 			Data:   data,
diff --git a/codegen/service/example_interceptors.go b/codegen/service/example_interceptors.go
index 7c24b08b..c8187d00 100644
--- a/codegen/service/example_interceptors.go
+++ b/codegen/service/example_interceptors.go
@@ -11,10 +11,10 @@ import (
 )
 
 // ExampleInterceptorsFiles returns the files for the example server and client interceptors.
-func ExampleInterceptorsFiles(genpkg string, r *expr.RootExpr) []*codegen.File {
+func ExampleInterceptorsFiles(genpkg string, r *expr.RootExpr, services *ServicesData) []*codegen.File {
 	var fw []*codegen.File
 	for _, svc := range r.Services {
-		if f := exampleInterceptorsFile(genpkg, svc); f != nil {
+		if f := exampleInterceptorsFile(genpkg, svc, services); f != nil {
 			fw = append(fw, f...)
 		}
 	}
@@ -22,8 +22,8 @@ func ExampleInterceptorsFiles(genpkg string, r *expr.RootExpr) []*codegen.File {
 }
 
 // exampleInterceptorsFile returns the example interceptors for the given service.
-func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.File {
-	sdata := Services.Get(svc.Name)
+func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr, services *ServicesData) []*codegen.File {
+	sdata := services.Get(svc.Name)
 	data := map[string]any{
 		"ServiceName":        sdata.Name,
 		"StructName":         sdata.StructName,
diff --git a/codegen/service/example_svc.go b/codegen/service/example_svc.go
index 46b977ef..d095a581 100644
--- a/codegen/service/example_svc.go
+++ b/codegen/service/example_svc.go
@@ -35,12 +35,12 @@ type (
 
 // ExampleServiceFiles returns a basic service implementation for every
 // service expression.
-func ExampleServiceFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ExampleServiceFiles(genpkg string, root *expr.RootExpr, services *ServicesData) []*codegen.File {
 
 	// determine the unique API package name different from the service names
 	scope := codegen.NewNameScope()
 	for _, svc := range root.Services {
-		s := Services.Get(svc.Name)
+		s := services.Get(svc.Name)
 		if s == nil {
 			panic("unknown service, " + svc.Name) // bug
 		}
@@ -50,7 +50,7 @@ func ExampleServiceFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 
 	var fw []*codegen.File
 	for _, svc := range root.Services {
-		if f := exampleServiceFile(genpkg, root, svc, apipkg); f != nil {
+		if f := exampleServiceFile(genpkg, root, svc, services, apipkg); f != nil {
 			fw = append(fw, f)
 		}
 	}
@@ -58,8 +58,8 @@ func ExampleServiceFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 }
 
 // exampleServiceFile returns a basic implementation of the given service.
-func exampleServiceFile(genpkg string, _ *expr.RootExpr, svc *expr.ServiceExpr, apipkg string) *codegen.File {
-	data := Services.Get(svc.Name)
+func exampleServiceFile(genpkg string, _ *expr.RootExpr, svc *expr.ServiceExpr, services *ServicesData, apipkg string) *codegen.File {
+	data := services.Get(svc.Name)
 	svcName := data.PathName
 	fpath := svcName + ".go"
 	if _, err := os.Stat(fpath); !os.IsNotExist(err) {
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
index ef70bd29..3602894d 100644
--- a/codegen/service/interceptors.go
+++ b/codegen/service/interceptors.go
@@ -8,9 +8,9 @@ import (
 )
 
 // InterceptorsFiles returns the interceptors files for the given service.
-func InterceptorsFiles(_ string, service *expr.ServiceExpr) []*codegen.File {
+func InterceptorsFiles(_ string, service *expr.ServiceExpr, services *ServicesData) []*codegen.File {
 	var files []*codegen.File
-	svc := Services.Get(service.Name)
+	svc := services.Get(service.Name)
 
 	// Generate service-specific interceptor files
 	if len(svc.ServerInterceptors) > 0 {
diff --git a/codegen/service/service.go b/codegen/service/service.go
index bc900ce7..61114d67 100644
--- a/codegen/service/service.go
+++ b/codegen/service/service.go
@@ -13,8 +13,8 @@ import (
 // indexing user type names by custom path as defined by the "struct:pkg:path"
 // metadata. The map is built over each invocation of Files to avoid duplicate
 // type definitions.
-func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]string) []*codegen.File {
-	svc := Services.Get(service.Name)
+func Files(genpkg string, service *expr.ServiceExpr, services *ServicesData, userTypePkgs map[string][]string) []*codegen.File {
+	svc := services.Get(service.Name)
 	svc.initUserTypeImports(genpkg)
 	svcName := svc.PathName
 	svcPath := filepath.Join(codegen.Gendir, svcName, "service.go")
@@ -189,7 +189,7 @@ func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]s
 	files := []*codegen.File{{Path: svcPath, SectionTemplates: sections}}
 
 	// service and client interceptors
-	files = append(files, InterceptorsFiles(genpkg, service)...)
+	files = append(files, InterceptorsFiles(genpkg, service, services)...)
 
 	// user types
 	paths := make([]string, len(typeDefSections))
@@ -233,19 +233,18 @@ func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]s
 }
 
 // AddServiceDataMetaTypeImports Adds all imports defined by struct:field:type from the service expr and the service data
-func AddServiceDataMetaTypeImports(header *codegen.SectionTemplate, serviceE *expr.ServiceExpr) {
-	codegen.AddServiceMetaTypeImports(header, serviceE)
-	svc := Services.Get(serviceE.Name)
-	for _, ut := range svc.userTypes {
+func AddServiceDataMetaTypeImports(header *codegen.SectionTemplate, svcExpr *expr.ServiceExpr, svcData *Data) {
+	codegen.AddServiceMetaTypeImports(header, svcExpr)
+	for _, ut := range svcData.userTypes {
 		codegen.AddImport(header, codegen.GetMetaTypeImports(ut.Type.Attribute())...)
 	}
-	for _, et := range svc.errorTypes {
+	for _, et := range svcData.errorTypes {
 		codegen.AddImport(header, codegen.GetMetaTypeImports(et.Type.Attribute())...)
 	}
-	for _, t := range svc.viewedResultTypes {
+	for _, t := range svcData.viewedResultTypes {
 		codegen.AddImport(header, codegen.GetMetaTypeImports(t.Type.Attribute())...)
 	}
-	for _, t := range svc.projectedTypes {
+	for _, t := range svcData.projectedTypes {
 		codegen.AddImport(header, codegen.GetMetaTypeImports(t.Type.Attribute())...)
 	}
 }
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index e1cbb366..11e76791 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -11,10 +11,6 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// Services holds the data computed from the design needed to generate the code
-// of the services.
-var Services = make(ServicesData)
-
 var (
 	// initTypeTmpl is the template used to render the code that initializes a
 	// projected type or viewed result type or a result type.
@@ -35,7 +31,10 @@ var (
 
 type (
 	// ServicesData encapsulates the data computed from the service designs.
-	ServicesData map[string]*Data
+	ServicesData struct {
+		Root     *expr.RootExpr
+		Services map[string]*Data
+	}
 
 	// Data contains the data used to render the code related to a single
 	// service.
@@ -567,18 +566,26 @@ type (
 	}
 )
 
+// NewServicesData creates a new ServicesData instance for the given root.
+func NewServicesData(root *expr.RootExpr) *ServicesData {
+	return &ServicesData{
+		Services: make(map[string]*Data),
+		Root:     root,
+	}
+}
+
 // Get retrieves the data for the service with the given name computing it if
 // needed. It returns nil if there is no service with the given name.
-func (d ServicesData) Get(name string) *Data {
-	if data, ok := d[name]; ok {
+func (d *ServicesData) Get(name string) *Data {
+	if data, ok := d.Services[name]; ok {
 		return data
 	}
-	service := expr.Root.Service(name)
+	service := d.Root.Service(name)
 	if service == nil {
 		return nil
 	}
-	d[name] = d.analyze(service)
-	return d[name]
+	d.Services[name] = d.analyze(service)
+	return d.Services[name]
 }
 
 // Method returns the service method data for the method with the given name,
@@ -696,7 +703,7 @@ func (s SchemesData) DedupeByType() SchemesData {
 
 // analyze creates the data necessary to render the code of the given service.
 // It records the user types needed by the service definition in userTypes.
-func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
+func (d *ServicesData) analyze(service *expr.ServiceExpr) *Data {
 	var (
 		types            []*UserTypeData
 		errTypes         []*UserTypeData
@@ -778,7 +785,7 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 	}
 
 	// Add forced types
-	for _, t := range expr.Root.Types {
+	for _, t := range d.Root.Types {
 		svcs, ok := t.Attribute().Meta["type:generate:force"]
 		if !ok {
 			continue
@@ -804,7 +811,7 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 	)
 	methods = make([]*MethodData, len(service.Methods))
 	for i, e := range service.Methods {
-		m := buildMethodData(e, scope)
+		m := d.buildMethodData(e, scope)
 		methods[i] = m
 		for _, s := range m.Schemes {
 			schemes = schemes.Append(s)
@@ -877,8 +884,8 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 	data := &Data{
 		Name:               service.Name,
 		Description:        desc,
-		APIName:            expr.Root.API.Name,
-		APIVersion:         expr.Root.API.Version,
+		APIName:            d.Root.API.Name,
+		APIVersion:         d.Root.API.Version,
 		VarName:            varName,
 		PathName:           codegen.SnakeCase(varName),
 		StructName:         codegen.Goify(service.Name, true),
@@ -886,8 +893,8 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		ViewsPkg:           viewspkg,
 		Methods:            methods,
 		Schemes:            schemes,
-		ServerInterceptors: collectInterceptors(service, methods, scope, true),
-		ClientInterceptors: collectInterceptors(service, methods, scope, false),
+		ServerInterceptors: d.collectInterceptors(service, methods, scope, true),
+		ClientInterceptors: d.collectInterceptors(service, methods, scope, false),
 		Scope:              scope,
 		ViewScope:          viewScope,
 		errorTypes:         errTypes,
@@ -899,23 +906,23 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		unionValueMethods:  unionMethods,
 	}
 
-	d[service.Name] = data
+	d.Services[service.Name] = data
 
 	return data
 }
 
 // collectInterceptors returns the set of interceptors defined on the given
 // service including any interceptor defined on specific service methods or API.
-func collectInterceptors(svc *expr.ServiceExpr, methods []*MethodData, scope *codegen.NameScope, server bool) []*InterceptorData {
+func (d *ServicesData) collectInterceptors(svc *expr.ServiceExpr, methods []*MethodData, scope *codegen.NameScope, server bool) []*InterceptorData {
 	var ints []*expr.InterceptorExpr
 	if server {
-		ints = expr.Root.API.ServerInterceptors
+		ints = d.Root.API.ServerInterceptors
 		ints = append(ints, svc.ServerInterceptors...)
 		for _, m := range svc.Methods {
 			ints = append(ints, m.ServerInterceptors...)
 		}
 	} else {
-		ints = expr.Root.API.ClientInterceptors
+		ints = d.Root.API.ClientInterceptors
 		ints = append(ints, svc.ClientInterceptors...)
 		for _, m := range svc.Methods {
 			ints = append(ints, m.ClientInterceptors...)
@@ -1054,7 +1061,7 @@ func buildErrorInitData(er *expr.ErrorExpr, scope *codegen.NameScope) *ErrorInit
 
 // buildMethodData creates the data needed to render the given endpoint. It
 // records the user types needed by the service definition in userTypes.
-func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
+func (d *ServicesData) buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
 	var (
 		vname       string
 		desc        string
@@ -1092,7 +1099,7 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
 			payloadDesc = fmt.Sprintf("%s is the payload type of the %s service %s method.",
 				payloadName, m.Service.Name, m.Name)
 		}
-		payloadEx = m.Payload.Example(expr.Root.API.ExampleGenerator)
+		payloadEx = m.Payload.Example(d.Root.API.ExampleGenerator)
 	}
 	if m.Result.Type != expr.Empty {
 		rname = scope.GoTypeName(m.Result)
@@ -1106,7 +1113,7 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
 			resultDesc = fmt.Sprintf("%s is the result type of the %s service %s method.",
 				rname, m.Service.Name, m.Name)
 		}
-		resultEx = m.Result.Example(expr.Root.API.ExampleGenerator)
+		resultEx = m.Result.Example(d.Root.API.ExampleGenerator)
 	}
 	if len(m.Errors) > 0 {
 		errors = make([]*ErrorInitData, len(m.Errors))
@@ -1126,7 +1133,7 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
 		reqs = append(reqs, &RequirementData{Schemes: rs, Scopes: req.Scopes})
 	}
 	var httpMet *expr.HTTPEndpointExpr
-	if httpSvc := expr.Root.HTTPService(m.Service.Name); httpSvc != nil {
+	if httpSvc := d.Root.HTTPService(m.Service.Name); httpSvc != nil {
 		httpMet = httpSvc.Endpoint(m.Name)
 	}
 	data := &MethodData{
@@ -1156,12 +1163,12 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
 		RequestStruct:                vname + "RequestData",
 		ResponseStruct:               vname + "ResponseData",
 	}
-	initStreamData(data, m, vname, rname, resultRef, scope)
+	d.initStreamData(data, m, vname, rname, resultRef, scope)
 	return data
 }
 
 // initStreamData initializes the streaming payload data structures and methods.
-func initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRef string, scope *codegen.NameScope) {
+func (d *ServicesData) initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRef string, scope *codegen.NameScope) {
 	if !m.IsStreaming() {
 		return
 	}
@@ -1183,7 +1190,7 @@ func initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRe
 			spayloadDesc = fmt.Sprintf("%s is the streaming payload type of the %s service %s method.",
 				spayloadName, m.Service.Name, m.Name)
 		}
-		spayloadEx = m.StreamingPayload.Example(expr.Root.API.ExampleGenerator)
+		spayloadEx = m.StreamingPayload.Example(d.Root.API.ExampleGenerator)
 	}
 	svrStream := &StreamData{
 		Interface:           vname + "ServerStream",
diff --git a/codegen/service/views.go b/codegen/service/views.go
index 980eac18..f010461b 100644
--- a/codegen/service/views.go
+++ b/codegen/service/views.go
@@ -16,98 +16,91 @@ type viewedType struct {
 
 // ViewsFile returns the views file for the given service which contains
 // logic to render result types using the defined views.
-func ViewsFile(_ string, service *expr.ServiceExpr) *codegen.File {
-	svc := Services.Get(service.Name)
+func ViewsFile(_ string, service *expr.ServiceExpr, services *ServicesData) *codegen.File {
+	svc := services.Get(service.Name)
 	if len(svc.projectedTypes) == 0 {
 		return nil
 	}
 	path := filepath.Join(codegen.Gendir, svc.PathName, "views", "view.go")
+	header := codegen.Header(service.Name+" views", "views",
+		[]*codegen.ImportSpec{
+			codegen.GoaImport(""),
+			{Path: "unicode/utf8"},
+		})
+	sections := []*codegen.SectionTemplate{header}
+
+	// type definitions
+	for _, t := range svc.viewedResultTypes {
+		sections = append(sections, &codegen.SectionTemplate{
+			Name:   "viewed-result-type",
+			Source: readTemplate("user_type"),
+			Data:   t.UserTypeData,
+		})
+	}
+	for _, t := range svc.projectedTypes {
+		sections = append(sections, &codegen.SectionTemplate{
+			Name:   "projected-type",
+			Source: readTemplate("user_type"),
+			Data:   t.UserTypeData,
+		})
+	}
+
+	// Union methods
+	for _, m := range svc.viewedUnionMethods {
+		sections = append(sections, &codegen.SectionTemplate{
+			Name:   "viewed-union-value-method",
+			Source: readTemplate("union_value_method"),
+			Data:   m,
+		})
+	}
+
+	// generate a map for result types with view name as key and the fields
+	// rendered in the view as value.
 	var (
-		sections []*codegen.SectionTemplate
+		rtdata []*viewedType
+		seen   = make(map[string]struct{})
 	)
-	{
-		header := codegen.Header(service.Name+" views", "views",
-			[]*codegen.ImportSpec{
-				codegen.GoaImport(""),
-				{Path: "unicode/utf8"},
-			})
-		sections = []*codegen.SectionTemplate{header}
-
-		// type definitions
-		for _, t := range svc.viewedResultTypes {
-			sections = append(sections, &codegen.SectionTemplate{
-				Name:   "viewed-result-type",
-				Source: readTemplate("user_type"),
-				Data:   t.UserTypeData,
-			})
+	for _, t := range svc.viewedResultTypes {
+		name := t.Views[0].TypeVarName
+		if _, ok := seen[name]; !ok {
+			rtdata = append(rtdata, &viewedType{Name: name, Views: t.Views})
+			seen[name] = struct{}{}
 		}
-		for _, t := range svc.projectedTypes {
-			sections = append(sections, &codegen.SectionTemplate{
-				Name:   "projected-type",
-				Source: readTemplate("user_type"),
-				Data:   t.UserTypeData,
-			})
+	}
+	for _, t := range svc.projectedTypes {
+		if len(t.Views) == 0 {
+			continue
 		}
-
-		// Union methods
-		for _, m := range svc.viewedUnionMethods {
-			sections = append(sections, &codegen.SectionTemplate{
-				Name:   "viewed-union-value-method",
-				Source: readTemplate("union_value_method"),
-				Data:   m,
-			})
+		name := t.Views[0].TypeVarName
+		if _, ok := seen[name]; !ok {
+			rtdata = append(rtdata, &viewedType{Name: name, Views: t.Views})
+			seen[name] = struct{}{}
 		}
+	}
+	sections = append(sections, &codegen.SectionTemplate{
+		Name:   "viewed-type-map",
+		Source: readTemplate("viewed_type_map"),
+		Data: map[string]any{
+			"ViewedTypes": rtdata,
+		},
+	})
 
-		// generate a map for result types with view name as key and the fields
-		// rendered in the view as value.
-		var (
-			rtdata []*viewedType
-			seen   = make(map[string]struct{})
-		)
-		{
-			for _, t := range svc.viewedResultTypes {
-				name := t.Views[0].TypeVarName
-				if _, ok := seen[name]; !ok {
-					rtdata = append(rtdata, &viewedType{Name: name, Views: t.Views})
-					seen[name] = struct{}{}
-				}
-			}
-			for _, t := range svc.projectedTypes {
-				if len(t.Views) == 0 {
-					continue
-				}
-				name := t.Views[0].TypeVarName
-				if _, ok := seen[name]; !ok {
-					rtdata = append(rtdata, &viewedType{Name: name, Views: t.Views})
-					seen[name] = struct{}{}
-				}
-			}
-		}
+	// validations
+	for _, t := range svc.viewedResultTypes {
 		sections = append(sections, &codegen.SectionTemplate{
-			Name:   "viewed-type-map",
-			Source: readTemplate("viewed_type_map"),
-			Data: map[string]any{
-				"ViewedTypes": rtdata,
-			},
+			Name:   "validate-viewed-result-type",
+			Source: readTemplate("validate"),
+			Data:   t.Validate,
 		})
-
-		// validations
-		for _, t := range svc.viewedResultTypes {
+	}
+	for _, t := range svc.projectedTypes {
+		for _, v := range t.Validations {
 			sections = append(sections, &codegen.SectionTemplate{
-				Name:   "validate-viewed-result-type",
+				Name:   "validate-projected-type",
 				Source: readTemplate("validate"),
-				Data:   t.Validate,
+				Data:   v,
 			})
 		}
-		for _, t := range svc.projectedTypes {
-			for _, v := range t.Validations {
-				sections = append(sections, &codegen.SectionTemplate{
-					Name:   "validate-projected-type",
-					Source: readTemplate("validate"),
-					Data:   v,
-				})
-			}
-		}
 	}
 
 	return &codegen.File{Path: path, SectionTemplates: sections}
diff --git a/grpc/codegen/client.go b/grpc/codegen/client.go
index f6669772..a76fd289 100644
--- a/grpc/codegen/client.go
+++ b/grpc/codegen/client.go
@@ -9,29 +9,27 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// ClientFiles returns the client implementation for every gRPC service. The
-// files include the client which invokes the protoc-generated gRPC client
-// and encoders and decoders to transform protocol buffer types and gRPC
-// metadata into goa types and vice versa.
-func ClientFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	svcLen := len(root.API.GRPC.Services)
+// ClientFiles returns the client files that contain client methods to call the
+// corresponding service methods along with the encoding and decoding logic.
+func ClientFiles(genpkg string, services *ServicesData) []*codegen.File {
+	svcLen := len(services.Root.API.GRPC.Services)
 	fw := make([]*codegen.File, 2*svcLen)
-	for i, svc := range root.API.GRPC.Services {
-		fw[i] = client(genpkg, svc)
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i] = clientFile(genpkg, svc, services)
 	}
-	for i, svc := range root.API.GRPC.Services {
-		fw[i+svcLen] = clientEncodeDecode(genpkg, svc)
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i+svcLen] = clientEncodeDecode(genpkg, svc, services)
 	}
 	return fw
 }
 
-// client returns the files defining the gRPC client.
-func client(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
+// clientFile returns the file implementing the gRPC client.
+func clientFile(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		fpath    string
 		sections []*codegen.SectionTemplate
 
-		data = GRPCServices.Get(svc.Name())
+		data = services.Get(svc.Name())
 	)
 	{
 		svcName := data.Service.PathName
@@ -112,12 +110,14 @@ func client(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
 	return &codegen.File{Path: fpath, SectionTemplates: sections}
 }
 
-func clientEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
+// clientEncodeDecode returns the file containing the gRPC client encoding and
+// decoding logic.
+func clientEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		fpath    string
 		sections []*codegen.SectionTemplate
 
-		data = GRPCServices.Get(svc.Name())
+		data = services.Get(svc.Name())
 	)
 	{
 		svcName := data.Service.PathName
@@ -137,7 +137,7 @@ func clientEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File
 		}
 		imports = append(imports, data.Service.UserTypeImports...)
 		sections = []*codegen.SectionTemplate{codegen.Header(svc.Name()+" gRPC client encoders and decoders", "client", imports)}
-		fm := transTmplFuncs(svc)
+		fm := transTmplFuncs(svc, services)
 		fm["metadataEncodeDecodeData"] = metadataEncodeDecodeData
 		fm["typeConversionData"] = typeConversionData
 		fm["isBearer"] = isBearer
diff --git a/grpc/codegen/client_cli.go b/grpc/codegen/client_cli.go
index 4f6c2b8d..80ae5255 100644
--- a/grpc/codegen/client_cli.go
+++ b/grpc/codegen/client_cli.go
@@ -11,19 +11,19 @@ import (
 
 // ClientCLIFiles returns the CLI files to generate a command-line client that
 // makes gRPC requests.
-func ClientCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	if len(root.API.GRPC.Services) == 0 {
+func ClientCLIFiles(genpkg string, services *ServicesData) []*codegen.File {
+	if len(services.Root.API.GRPC.Services) == 0 {
 		return nil
 	}
 	var (
 		data []*cli.CommandData
 		svcs []*expr.GRPCServiceExpr
 	)
-	for _, svc := range root.API.GRPC.Services {
+	for _, svc := range services.Root.API.GRPC.Services {
 		if len(svc.GRPCEndpoints) == 0 {
 			continue
 		}
-		sd := GRPCServices.Get(svc.Name())
+		sd := services.Get(svc.Name())
 		command := cli.BuildCommandData(sd.Service)
 		for _, e := range sd.Endpoints {
 			flags, buildFunction := buildFlags(e)
@@ -35,18 +35,18 @@ func ClientCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 		svcs = append(svcs, svc)
 	}
 	var files []*codegen.File
-	for _, svr := range root.API.Servers {
-		files = append(files, endpointParser(genpkg, root, svr, data))
+	for _, svr := range services.Root.API.Servers {
+		files = append(files, endpointParser(genpkg, services, svr, data))
 	}
 	for i, svc := range svcs {
-		files = append(files, payloadBuilders(genpkg, svc, data[i]))
+		files = append(files, payloadBuilders(genpkg, svc, data[i], services))
 	}
 	return files
 }
 
 // endpointParser returns the file that implements the command line parser that
 // builds the client endpoint and payload necessary to perform a request.
-func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, data []*cli.CommandData) *codegen.File {
+func endpointParser(genpkg string, services *ServicesData, svr *expr.ServerExpr, data []*cli.CommandData) *codegen.File {
 	pkg := codegen.SnakeCase(codegen.Goify(svr.Name, true))
 	fpath := filepath.Join(codegen.Gendir, "grpc", "cli", pkg, "cli.go")
 	title := svr.Name + " gRPC client CLI support package"
@@ -61,8 +61,8 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
 		codegen.GoaNamedImport("grpc", "goagrpc"),
 		{Path: "google.golang.org/grpc", Name: "grpc"},
 	}
-	for _, svc := range root.API.GRPC.Services {
-		sd := GRPCServices.Get(svc.Name())
+	for _, svc := range services.Root.API.GRPC.Services {
+		sd := services.Get(svc.Name())
 		if sd == nil {
 			continue
 		}
@@ -104,8 +104,8 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
 
 // payloadBuilders returns the file that contains the payload constructors that
 // use flag values as arguments.
-func payloadBuilders(genpkg string, svc *expr.GRPCServiceExpr, data *cli.CommandData) *codegen.File {
-	sd := GRPCServices.Get(svc.Name())
+func payloadBuilders(genpkg string, svc *expr.GRPCServiceExpr, data *cli.CommandData, services *ServicesData) *codegen.File {
+	sd := services.Get(svc.Name())
 	svcName := sd.Service.PathName
 	fpath := filepath.Join(codegen.Gendir, "grpc", svcName, "client", "cli.go")
 	title := svc.Name() + " gRPC client CLI support package"
diff --git a/grpc/codegen/client_types.go b/grpc/codegen/client_types.go
index f6d51e5e..c7ed6ce4 100644
--- a/grpc/codegen/client_types.go
+++ b/grpc/codegen/client_types.go
@@ -8,31 +8,22 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// ClientTypeFiles returns the types file for every gRPC service that contain
-// constructors to transform:
-//
-//   - service payload types into protocol buffer request message types
-//   - protocol buffer response message types into service result types
-func ClientTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	fw := make([]*codegen.File, len(root.API.GRPC.Services))
-	seen := make(map[string]struct{})
-	for i, r := range root.API.GRPC.Services {
-		fw[i] = clientType(genpkg, r, seen)
+// ClientTypeFiles returns the client types files containing all the client
+// interfaces and types needed to implement gRPC client.
+func ClientTypeFiles(genpkg string, services *ServicesData) []*codegen.File {
+	fw := make([]*codegen.File, len(services.Root.API.GRPC.Services))
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i] = clientType(genpkg, svc, services)
 	}
 	return fw
 }
 
-// clientType returns the file containing the constructor functions to
-// transform the service payload types to the corresponding gRPC request types
-// and gRPC response types to the corresponding service result types.
-//
-// seen keeps track of the constructor names that have already been generated
-// to prevent duplicate code generation.
-func clientType(genpkg string, svc *expr.GRPCServiceExpr, _ map[string]struct{}) *codegen.File {
+// clientType returns the file defining the gRPC client types.
+func clientType(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		initData []*InitData
 
-		sd = GRPCServices.Get(svc.Name())
+		sd = services.Get(svc.Name())
 	)
 	{
 		seen := make(map[string]struct{})
diff --git a/grpc/codegen/example_cli.go b/grpc/codegen/example_cli.go
index 8e346f5c..23794b9e 100644
--- a/grpc/codegen/example_cli.go
+++ b/grpc/codegen/example_cli.go
@@ -12,10 +12,10 @@ import (
 )
 
 // ExampleCLIFiles returns an example gRPC client tool implementation.
-func ExampleCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ExampleCLIFiles(genpkg string, services *ServicesData) []*codegen.File {
 	var files []*codegen.File
-	for _, svr := range root.API.Servers {
-		if f := exampleCLI(genpkg, root, svr); f != nil {
+	for _, svr := range services.Root.API.Servers {
+		if f := exampleCLI(genpkg, services, svr); f != nil {
 			files = append(files, f)
 		}
 	}
@@ -24,68 +24,56 @@ func ExampleCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 
 // exampleCLI returns an example client tool HTTP implementation for the given
 // server expression.
-func exampleCLI(genpkg string, _ *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
+func exampleCLI(genpkg string, services *ServicesData, svr *expr.ServerExpr) *codegen.File {
 	var (
 		mainPath string
 		rootPath string
 
-		svrdata = example.Servers.Get(svr)
+		svrdata = example.Servers.Get(svr, services.Root)
 	)
-	{
-		mainPath = filepath.Join("cmd", svrdata.Dir+"-cli", "grpc.go")
-		if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
-			return nil // file already exists, skip it.
-		}
-		idx := strings.LastIndex(genpkg, string("/"))
-		rootPath = "."
-		if idx > 0 {
-			rootPath = genpkg[:idx]
-		}
+	mainPath = filepath.Join("cmd", svrdata.Dir+"-cli", "grpc.go")
+	if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
+		return nil // file already exists, skip it.
+	}
+	idx := strings.LastIndex(genpkg, string("/"))
+	rootPath = "."
+	if idx > 0 {
+		rootPath = genpkg[:idx]
 	}
 
-	var (
-		specs []*codegen.ImportSpec
-	)
-	{
-		specs = []*codegen.ImportSpec{
-			{Path: "context"},
-			{Path: "encoding/json"},
-			{Path: "flag"},
-			{Path: "fmt"},
-			{Path: "google.golang.org/grpc"},
-			{Path: "google.golang.org/grpc/credentials/insecure"},
-			{Path: "os"},
-			{Path: "time"},
-			codegen.GoaImport(""),
-			codegen.GoaNamedImport("grpc", "goagrpc"),
-			{Path: rootPath + "/interceptors"},
-			{Path: path.Join(genpkg, "grpc", "cli", svrdata.Dir), Name: "cli"},
-		}
+	specs := []*codegen.ImportSpec{
+		{Path: "context"},
+		{Path: "encoding/json"},
+		{Path: "flag"},
+		{Path: "fmt"},
+		{Path: "google.golang.org/grpc"},
+		{Path: "google.golang.org/grpc/credentials/insecure"},
+		{Path: "os"},
+		{Path: "time"},
+		codegen.GoaImport(""),
+		codegen.GoaNamedImport("grpc", "goagrpc"),
+		{Path: rootPath + "/interceptors"},
+		{Path: path.Join(genpkg, "grpc", "cli", svrdata.Dir), Name: "cli"},
 	}
 
 	var svcData []*ServiceData
 	for _, svc := range svr.Services {
-		if data := GRPCServices.Get(svc); data != nil {
+		if data := services.Get(svc); data != nil {
 			svcData = append(svcData, data)
 		}
 	}
 
-	var (
-		sections []*codegen.SectionTemplate
-	)
-	{
-		sections = []*codegen.SectionTemplate{
-			codegen.Header("", "main", specs),
-			{
-				Name:   "do-grpc-cli",
-				Source: readTemplate("do_grpc_cli"),
-				Data: map[string]any{
-					"DefaultTransport": svrdata.DefaultTransport(),
-					"Services":         svcData,
-					"InterceptorsPkg":  "interceptors",
-				},
+	sections := []*codegen.SectionTemplate{
+		codegen.Header("", "main", specs),
+		{
+			Name:   "do-grpc-cli",
+			Source: readTemplate("do_grpc_cli"),
+			Data: map[string]any{
+				"DefaultTransport": svrdata.DefaultTransport(),
+				"Services":         svcData,
+				"InterceptorsPkg":  "interceptors",
 			},
-		}
+		},
 	}
 
 	return &codegen.File{Path: mainPath, SectionTemplates: sections, SkipExist: true}
diff --git a/grpc/codegen/example_server.go b/grpc/codegen/example_server.go
index f4a712bb..e651a192 100644
--- a/grpc/codegen/example_server.go
+++ b/grpc/codegen/example_server.go
@@ -12,10 +12,10 @@ import (
 )
 
 // ExampleServerFiles returns an example gRPC server implementation.
-func ExampleServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ExampleServerFiles(genpkg string, services *ServicesData) []*codegen.File {
 	var fw []*codegen.File
-	for _, svr := range root.API.Servers {
-		if m := exampleServer(genpkg, root, svr); m != nil {
+	for _, svr := range services.Root.API.Servers {
+		if m := exampleServer(genpkg, services, svr); m != nil {
 			fw = append(fw, m)
 		}
 	}
@@ -23,17 +23,15 @@ func ExampleServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 }
 
 // exampleServer returns an example gRPC server implementation.
-func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
+func exampleServer(genpkg string, services *ServicesData, svr *expr.ServerExpr) *codegen.File {
 	var (
 		mainPath string
 
-		svrdata = example.Servers.Get(svr)
+		svrdata = example.Servers.Get(svr, services.Root)
 	)
-	{
-		mainPath = filepath.Join("cmd", svrdata.Dir, "grpc.go")
-		if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
-			return nil // file already exists, skip it.
-		}
+	mainPath = filepath.Join("cmd", svrdata.Dir, "grpc.go")
+	if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
+		return nil // file already exists, skip it.
 	}
 
 	var (
@@ -41,94 +39,89 @@ func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *co
 
 		scope = codegen.NewNameScope()
 	)
-	{
-		specs = []*codegen.ImportSpec{
-			{Path: "context"},
-			{Path: "fmt"},
-			{Path: "net"},
-			{Path: "net/url"},
-			{Path: "sync"},
-			codegen.GoaNamedImport("grpc", "goagrpc"),
-			{Path: "goa.design/clue/debug"},
-			{Path: "goa.design/clue/log"},
-			{Path: "google.golang.org/grpc"},
-			{Path: "google.golang.org/grpc/reflection"},
-		}
-		for _, svc := range root.API.GRPC.Services {
-			sd := GRPCServices.Get(svc.Name())
-			svcName := sd.Service.PathName
-			specs = append(specs, &codegen.ImportSpec{
+	specs = []*codegen.ImportSpec{
+		{Path: "context"},
+		{Path: "fmt"},
+		{Path: "net"},
+		{Path: "net/url"},
+		{Path: "sync"},
+		codegen.GoaNamedImport("grpc", "goagrpc"),
+		{Path: "goa.design/clue/debug"},
+		{Path: "goa.design/clue/log"},
+		{Path: "google.golang.org/grpc"},
+		{Path: "google.golang.org/grpc/reflection"},
+	}
+	for _, svc := range services.Root.API.GRPC.Services {
+		sd := services.Get(svc.Name())
+		svcName := sd.Service.PathName
+		specs = append(specs,
+			&codegen.ImportSpec{
 				Path: path.Join(genpkg, "grpc", svcName, "server"),
 				Name: scope.Unique(sd.Service.PkgName + "svr"),
-			})
-			specs = append(specs, &codegen.ImportSpec{
+			},
+			&codegen.ImportSpec{
 				Path: path.Join(genpkg, svcName),
 				Name: scope.Unique(sd.Service.PkgName),
-			})
-			specs = append(specs, &codegen.ImportSpec{
+			},
+			&codegen.ImportSpec{
 				Path: path.Join(genpkg, "grpc", svcName, pbPkgName),
 				Name: scope.Unique(svcName + pbPkgName),
 			})
-		}
 	}
 
 	var (
 		rootPath string
 		apiPkg   string
 	)
-	{
-		// genpkg is created by path.Join so the separator is / regardless of operating system
-		idx := strings.LastIndex(genpkg, string("/"))
-		rootPath = "."
-		if idx > 0 {
-			rootPath = genpkg[:idx]
-		}
-		apiPkg = scope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
+	// genpkg is created by path.Join so the separator is / regardless of operating system
+	idx := strings.LastIndex(genpkg, string("/"))
+	rootPath = "."
+	if idx > 0 {
+		rootPath = genpkg[:idx]
 	}
+	apiPkg = scope.Unique(strings.ToLower(codegen.Goify(services.Root.API.Name, false)), "api")
 	specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: apiPkg})
 
 	var (
 		sections []*codegen.SectionTemplate
 	)
-	{
-		var svcdata []*ServiceData
-		for _, svc := range svr.Services {
-			if data := GRPCServices.Get(svc); data != nil {
-				svcdata = append(svcdata, data)
-			}
+	var svcdata []*ServiceData
+	for _, svc := range svr.Services {
+		if data := services.Get(svc); data != nil {
+			svcdata = append(svcdata, data)
 		}
-		sections = []*codegen.SectionTemplate{
-			codegen.Header("", "main", specs),
-			{
-				Name:   "server-grpc-start",
-				Source: readTemplate("server_grpc_start"),
-				Data: map[string]any{
-					"Services": svcdata,
-				},
-			}, {
-				Name:   "server-grpc-init",
-				Source: readTemplate("server_grpc_init"),
-				Data: map[string]any{
-					"Services": svcdata,
-				},
-			}, {
-				Name:   "server-grpc-register",
-				Source: readTemplate("server_grpc_register"),
-				Data: map[string]any{
-					"Services": svcdata,
-				},
-				FuncMap: map[string]any{
-					"goify":      codegen.Goify,
-					"needStream": needStream,
-				},
-			}, {
-				Name:   "server-grpc-end",
-				Source: readTemplate("server_grpc_end"),
-				Data: map[string]any{
-					"Services": svcdata,
-				},
+	}
+	sections = []*codegen.SectionTemplate{
+		codegen.Header("", "main", specs),
+		{
+			Name:   "server-grpc-start",
+			Source: readTemplate("server_grpc_start"),
+			Data: map[string]any{
+				"Services": svcdata,
 			},
-		}
+		}, {
+			Name:   "server-grpc-init",
+			Source: readTemplate("server_grpc_init"),
+			Data: map[string]any{
+				"Services": svcdata,
+			},
+		}, {
+			Name:   "server-grpc-register",
+			Source: readTemplate("server_grpc_register"),
+			Data: map[string]any{
+				"Services": svcdata,
+			},
+			FuncMap: map[string]any{
+				"goify":      codegen.Goify,
+				"needStream": needStream,
+			},
+		}, {
+			Name:   "server-grpc-end",
+			Source: readTemplate("server_grpc_end"),
+			Data: map[string]any{
+				"Services": svcdata,
+			},
+		},
 	}
 	return &codegen.File{Path: mainPath, SectionTemplates: sections, SkipExist: true}
 }
diff --git a/grpc/codegen/proto.go b/grpc/codegen/proto.go
index 09598be2..ad571288 100644
--- a/grpc/codegen/proto.go
+++ b/grpc/codegen/proto.go
@@ -20,17 +20,18 @@ const (
 	ProtoPrefix = "goagen"
 )
 
-// ProtoFiles returns a *.proto file for each gRPC service.
-func ProtoFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	fw := make([]*codegen.File, len(root.API.GRPC.Services))
-	for i, svc := range root.API.GRPC.Services {
-		fw[i] = protoFile(genpkg, svc)
+// ProtoFiles returns the protobuf file for every gRPC service.
+func ProtoFiles(genpkg string, services *ServicesData) []*codegen.File {
+	fw := make([]*codegen.File, len(services.Root.API.GRPC.Services))
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i] = protoFile(genpkg, svc, services)
 	}
 	return fw
 }
 
-func protoFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
-	data := GRPCServices.Get(svc.Name())
+// protoFile returns the protobuf file defining the specified service.
+func protoFile(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	svcName := data.Service.PathName
 	parts := strings.Split(genpkg, "/")
 	var repoName string
@@ -82,10 +83,10 @@ func protoFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
 
 	runProtoc := func(path string) error {
 		includes := svc.ServiceExpr.Meta["protoc:include"]
-		includes = append(includes, expr.Root.API.Meta["protoc:include"]...)
+		includes = append(includes, services.Root.API.Meta["protoc:include"]...)
 
 		cmd := defaultProtocCmd
-		if c, ok := expr.Root.API.Meta["protoc:cmd"]; ok {
+		if c, ok := services.Root.API.Meta["protoc:cmd"]; ok {
 			cmd = c
 		}
 		if c, ok := svc.ServiceExpr.Meta["protoc:cmd"]; ok {
diff --git a/grpc/codegen/server.go b/grpc/codegen/server.go
index 4ec23353..1bc9490b 100644
--- a/grpc/codegen/server.go
+++ b/grpc/codegen/server.go
@@ -6,7 +6,6 @@ import (
 	"path/filepath"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/codegen/service"
 	"goa.design/goa/v3/expr"
 )
 
@@ -14,25 +13,25 @@ import (
 // contain the server which implements the generated gRPC server interface and
 // encoders and decoders to transform protocol buffer types and gRPC metadata
 // into goa types and vice versa.
-func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	svcLen := len(root.API.GRPC.Services)
+func ServerFiles(genpkg string, services *ServicesData) []*codegen.File {
+	svcLen := len(services.Root.API.GRPC.Services)
 	fw := make([]*codegen.File, 2*svcLen)
-	for i, svc := range root.API.GRPC.Services {
-		fw[i] = serverFile(genpkg, svc)
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i] = serverFile(genpkg, svc, services)
 	}
-	for i, svc := range root.API.GRPC.Services {
-		fw[i+svcLen] = serverEncodeDecode(genpkg, svc)
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i+svcLen] = serverEncodeDecode(genpkg, svc, services)
 	}
 	return fw
 }
 
 // serverFile returns the files defining the gRPC server.
-func serverFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
+func serverFile(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		fpath    string
 		sections []*codegen.SectionTemplate
 
-		data = GRPCServices.Get(svc.Name())
+		data = services.Get(svc.Name())
 	)
 	{
 		svcName := data.Service.PathName
@@ -75,8 +74,7 @@ func serverFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
 				Name:   "grpc-handler-init",
 				Source: readTemplate("grpc_handler_init"),
 				Data:   e,
-			})
-			sections = append(sections, &codegen.SectionTemplate{
+			}, &codegen.SectionTemplate{
 				Name:   "server-grpc-interface",
 				Source: readTemplate("server_grpc_interface"),
 				Data:   e,
@@ -120,12 +118,12 @@ func serverFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
 
 // serverEncodeDecode returns the file defining the gRPC server encoding and
 // decoding logic.
-func serverEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
+func serverEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		fpath    string
 		sections []*codegen.SectionTemplate
 
-		data = GRPCServices.Get(svc.Name())
+		data = services.Get(svc.Name())
 	)
 	{
 		svcName := data.Service.PathName
@@ -160,7 +158,7 @@ func serverEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File
 				})
 			}
 			if e.PayloadRef != "" {
-				fm := transTmplFuncs(svc)
+				fm := transTmplFuncs(svc, services)
 				fm["isEmpty"] = isEmpty
 				sections = append(sections, &codegen.SectionTemplate{
 					Name:    "request-decoder",
@@ -174,17 +172,17 @@ func serverEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File
 	return &codegen.File{Path: fpath, SectionTemplates: sections}
 }
 
-func transTmplFuncs(s *expr.GRPCServiceExpr) map[string]any {
+func transTmplFuncs(s *expr.GRPCServiceExpr, services *ServicesData) map[string]any {
 	return map[string]any{
 		"goTypeRef": func(dt expr.DataType) string {
-			return service.Services.Get(s.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
+			return services.ServicesData.Get(s.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
 		},
 	}
 }
 
 // typeConversionData produces the template data suitable for executing the
 // "type_conversion" template.
-func typeConversionData(dt expr.DataType, varName string, target string) map[string]any {
+func typeConversionData(dt expr.DataType, varName, target string) map[string]any {
 	return map[string]any{
 		"Type":    dt,
 		"VarName": varName,
diff --git a/grpc/codegen/server_types.go b/grpc/codegen/server_types.go
index db64c42e..3f3b92a2 100644
--- a/grpc/codegen/server_types.go
+++ b/grpc/codegen/server_types.go
@@ -8,31 +8,22 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// ServerTypeFiles returns the types file for every gRPC service that contain
-// constructors to transform:
-//
-//   - protocol buffer request message types into service payload types
-//   - service result types into protocol buffer response message types
-func ServerTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
-	fw := make([]*codegen.File, len(root.API.GRPC.Services))
-	seen := make(map[string]struct{})
-	for i, r := range root.API.GRPC.Services {
-		fw[i] = serverType(genpkg, r, seen)
+// ServerTypeFiles returns the server types files containing all the server
+// interfaces and types needed to implement gRPC server.
+func ServerTypeFiles(genpkg string, services *ServicesData) []*codegen.File {
+	fw := make([]*codegen.File, len(services.Root.API.GRPC.Services))
+	for i, svc := range services.Root.API.GRPC.Services {
+		fw[i] = serverType(genpkg, svc, services)
 	}
 	return fw
 }
 
-// serverType returns the file containing the constructor functions to
-// transform the gRPC request types to the corresponding service payload types
-// and service result types to the corresponding gRPC response types.
-//
-// seen keeps track of the constructor names that have already been generated
-// to prevent duplicate code generation.
-func serverType(genpkg string, svc *expr.GRPCServiceExpr, _ map[string]struct{}) *codegen.File {
+// serverType returns the file defining the gRPC server types.
+func serverType(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData) *codegen.File {
 	var (
 		initData []*InitData
 
-		sd         = GRPCServices.Get(svc.Name())
+		sd         = services.Get(svc.Name())
 		foundInits = make(map[string]struct{})
 	)
 	{
diff --git a/grpc/codegen/service_data.go b/grpc/codegen/service_data.go
index 68678b72..9bc92b59 100644
--- a/grpc/codegen/service_data.go
+++ b/grpc/codegen/service_data.go
@@ -9,14 +9,13 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// GRPCServices holds the data computed from the design needed to generate the
-// transport code of the gRPC services.
-var GRPCServices = make(ServicesData)
-
 type (
 	// ServicesData contains the data computed from the gRPC service expressions
 	// indexed by service name.
-	ServicesData map[string]*ServiceData
+	ServicesData struct {
+		*service.ServicesData
+		GRPCServices map[string]*ServiceData
+	}
 
 	// ServiceData contains the data used to render the code related to a
 	// single service.
@@ -368,6 +367,14 @@ type (
 	validateKind int
 )
 
+// NewServicesData creates a new ServicesData instance for the given service data.
+func NewServicesData(services *service.ServicesData) *ServicesData {
+	return &ServicesData{
+		ServicesData:  services,
+		GRPCServices: make(map[string]*ServiceData),
+	}
+}
+
 const (
 	// pbPkgName is the directory name where the .proto file is generated and
 	// compiled.
@@ -389,16 +396,16 @@ const (
 // Get retrieves the transport data for the service with the given name
 // computing it if needed. It returns nil if there is no service with the given
 // name.
-func (d ServicesData) Get(name string) *ServiceData {
-	if data, ok := d[name]; ok {
+func (d *ServicesData) Get(name string) *ServiceData {
+	if data, ok := d.GRPCServices[name]; ok {
 		return data
 	}
-	service := expr.Root.API.GRPC.Service(name)
+	service := d.Root.API.GRPC.Service(name)
 	if service == nil {
 		return nil
 	}
-	d[name] = d.analyze(service)
-	return d[name]
+	d.GRPCServices[name] = d.analyze(service)
+	return d.GRPCServices[name]
 }
 
 // Endpoint returns the endpoint data for the endpoint with the given name, nil
@@ -434,8 +441,8 @@ func (sd *ServiceData) HasStreamingEndpoint() bool {
 }
 
 // analyze creates the data necessary to render the code of the given service.
-func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
-	svc := service.Services.Get(gs.Name())
+func (d *ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
+	svc := d.ServicesData.Get(gs.Name())
 	scope := codegen.NewNameScope()
 	pkg := codegen.SnakeCase(codegen.Goify(svc.Name, false)) + pbPkgName
 	svcVarN := scope.HashedUnique(gs.ServiceExpr, codegen.Goify(svc.Name, true))
@@ -517,7 +524,7 @@ func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 		if md.ViewedResult != nil {
 			viewedResultRef = md.ViewedResult.FullRef
 		}
-		errors := buildErrorsData(e, sd)
+		errors := d.buildErrorsData(e, sd)
 		for _, er := range e.GRPCErrors {
 			if er.Type == expr.ErrorResult || !expr.IsObject(er.Type) {
 				continue
@@ -526,12 +533,12 @@ func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 		}
 
 		// build request data
-		reqMD := extractMetadata(e.Metadata, e.MethodExpr.Payload, svc.Scope)
+		reqMD := extractMetadata(e.Metadata, e.MethodExpr.Payload, svc.Scope, *d)
 		request := &RequestData{
 			Description:   e.Request.Description,
 			Metadata:      reqMD,
-			ServerConvert: buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, true),
-			ClientConvert: buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, false),
+			ServerConvert: d.buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, true),
+			ClientConvert: d.buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, false),
 		}
 		if obj := expr.AsObject(e.Request.Type); (obj != nil && len(*obj) > 0) || expr.IsUnion(e.Request.Type) {
 			// add the request message as the first argument to the CLI
@@ -540,7 +547,7 @@ func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 				Ref:      "message",
 				TypeName: protoBufGoFullTypeName(e.Request, sd.PkgName, sd.Scope),
 				TypeRef:  protoBufGoFullTypeRef(e.Request, sd.PkgName, sd.Scope),
-				Example:  e.Request.Example(expr.Root.API.ExampleGenerator),
+				Example:  e.Request.Example(d.Root.API.ExampleGenerator),
 			})
 		}
 		// pass the metadata as arguments to client CLI args
@@ -568,15 +575,15 @@ func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 
 		// build response data
 		result, svcCtx := resultContext(e, sd)
-		hdrs := extractMetadata(e.Response.Headers, result, svc.Scope)
-		trlrs := extractMetadata(e.Response.Trailers, result, svc.Scope)
+		hdrs := extractMetadata(e.Response.Headers, result, svc.Scope, *d)
+		trlrs := extractMetadata(e.Response.Trailers, result, svc.Scope, *d)
 		response := &ResponseData{
 			StatusCode:    statusCodeToGRPCConst(e.Response.StatusCode),
 			Description:   e.Response.Description,
 			Headers:       hdrs,
 			Trailers:      trlrs,
-			ServerConvert: buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, true),
-			ClientConvert: buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, false),
+			ServerConvert: d.buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, true),
+			ClientConvert: d.buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, false),
 		}
 		// If the endpoint is a streaming endpoint, no message is returned
 		// by gRPC. Hence, no need to set response message.
@@ -623,8 +630,8 @@ func (ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 		}
 		sd.Endpoints = append(sd.Endpoints, ed)
 		if e.MethodExpr.IsStreaming() {
-			ed.ServerStream = buildStreamData(e, sd, true)
-			ed.ClientStream = buildStreamData(e, sd, false)
+			ed.ServerStream = d.buildStreamData(e, sd, true)
+			ed.ClientStream = d.buildStreamData(e, sd, false)
 		}
 	}
 	return sd
@@ -827,7 +834,7 @@ func userTypeAttribute(ut expr.UserType) *expr.AttributeExpr {
 //     type in *.pb.go.
 //
 // svr param indicates that the convert data is generated for server side.
-func buildRequestConvertData(request, payload *expr.AttributeExpr, md []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
+func (d *ServicesData) buildRequestConvertData(request, payload *expr.AttributeExpr, md []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
 	// Server-side: No need to build convert data if payload is empty or payload
 	// is not an object type and endpoint streams payload (the payload is
 	// encoded in metadata under "goa-payload" in this case).
@@ -843,7 +850,7 @@ func buildRequestConvertData(request, payload *expr.AttributeExpr, md []*Metadat
 	svcCtx := serviceTypeContext(pkg, svc.Scope)
 	if svr {
 		// server side
-		data := buildInitData(request, payload, "message", "v", svcCtx, false, svr, false, sd)
+		data := d.buildInitData(request, payload, "message", "v", svcCtx, false, svr, false, sd)
 		data.Name = fmt.Sprintf("New%sPayload", codegen.Goify(e.Name(), true))
 		data.Description = fmt.Sprintf("%s builds the payload of the %q endpoint of the %q service from the gRPC request type.", data.Name, e.Name(), svc.Name)
 		for _, m := range md {
@@ -873,7 +880,7 @@ func buildRequestConvertData(request, payload *expr.AttributeExpr, md []*Metadat
 	}
 
 	// client side
-	data := buildInitData(payload, request, "payload", "message", svcCtx, true, svr, false, sd)
+	data := d.buildInitData(payload, request, "payload", "message", svcCtx, true, svr, false, sd)
 	data.Description = fmt.Sprintf("%s builds the gRPC request type from the payload of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
 	return &ConvertData{
 		SrcName: svc.Scope.GoFullTypeName(payload, pkg),
@@ -892,14 +899,14 @@ func buildRequestConvertData(request, payload *expr.AttributeExpr, md []*Metadat
 //     response metadata to method result type.
 //
 // svr param indicates that the convert data is generated for server side.
-func buildResponseConvertData(response, result *expr.AttributeExpr, svcCtx *codegen.AttributeContext, hdrs, trlrs []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
+func (d *ServicesData) buildResponseConvertData(response, result *expr.AttributeExpr, svcCtx *codegen.AttributeContext, hdrs, trlrs []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
 	if !svr && (e.MethodExpr.IsStreaming() || isEmpty(e.MethodExpr.Result.Type)) {
 		return nil
 	}
 	svc := sd.Service
 	if svr {
 		// server side
-		data := buildInitData(result, response, "result", "message", svcCtx, true, svr, false, sd)
+		data := d.buildInitData(result, response, "result", "message", svcCtx, true, svr, false, sd)
 		data.Description = fmt.Sprintf("%s builds the gRPC response type from the result of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
 		return &ConvertData{
 			SrcName: svcCtx.Scope.Name(result, svcCtx.Pkg(result), svcCtx.Pointer, svcCtx.UseDefault),
@@ -911,7 +918,7 @@ func buildResponseConvertData(response, result *expr.AttributeExpr, svcCtx *code
 	}
 
 	// client side
-	data := buildInitData(response, result, "message", "result", svcCtx, false, svr, false, sd)
+	data := d.buildInitData(response, result, "message", "result", svcCtx, false, svr, false, sd)
 	data.Name = fmt.Sprintf("New%sResult", codegen.Goify(e.Name(), true))
 	data.Description = fmt.Sprintf("%s builds the result type of the %q endpoint of the %q service from the gRPC response type.", data.Name, e.Name(), svc.Name)
 	for _, m := range hdrs {
@@ -965,7 +972,7 @@ func buildResponseConvertData(response, result *expr.AttributeExpr, svcCtx *code
 // svcCtx is the attribute context for service type
 // proto if true indicates the target type is a protocol buffer type
 // svr if true indicates the code is generated for conversion server side
-func buildInitData(source, target *expr.AttributeExpr, sourceVar, targetVar string, svcCtx *codegen.AttributeContext, proto, _, usesrc bool, sd *ServiceData) *InitData {
+func (d *ServicesData) buildInitData(source, target *expr.AttributeExpr, sourceVar, targetVar string, svcCtx *codegen.AttributeContext, proto, _, usesrc bool, sd *ServiceData) *InitData {
 	pbCtx := protoBufTypeContext(sd.PkgName, sd.Scope, false)
 	name := "New"
 	srcCtx := pbCtx
@@ -998,7 +1005,7 @@ func buildInitData(source, target *expr.AttributeExpr, sourceVar, targetVar stri
 			Ref:      sourceVar,
 			TypeName: srcCtx.Scope.Name(source, srcCtx.Pkg(source), srcCtx.Pointer, srcCtx.UseDefault),
 			TypeRef:  srcCtx.Scope.Ref(source, srcCtx.Pkg(source)),
-			Example:  source.Example(expr.Root.API.ExampleGenerator),
+			Example:  source.Example(d.Root.API.ExampleGenerator),
 		}}
 	}
 	return &InitData{
@@ -1015,15 +1022,15 @@ func buildInitData(source, target *expr.AttributeExpr, sourceVar, targetVar stri
 // buildErrorsData builds the error data for all the error responses in the
 // endpoint expression. The response message for each error response are
 // inferred from the method's error expression if not specified explicitly.
-func buildErrorsData(e *expr.GRPCEndpointExpr, sd *ServiceData) []*ErrorData {
+func (d *ServicesData) buildErrorsData(e *expr.GRPCEndpointExpr, sd *ServiceData) []*ErrorData {
 	svc := sd.Service
 	errors := make([]*ErrorData, 0, len(e.GRPCErrors))
 	for _, v := range e.GRPCErrors {
 		responseData := &ResponseData{
 			StatusCode:    statusCodeToGRPCConst(v.Response.StatusCode),
 			Description:   v.Response.Description,
-			ServerConvert: buildErrorConvertData(v, e, sd, true),
-			ClientConvert: buildErrorConvertData(v, e, sd, false),
+			ServerConvert: d.buildErrorConvertData(v, e, sd, true),
+			ClientConvert: d.buildErrorConvertData(v, e, sd, false),
 		}
 		errorLoc := svc.Method(e.MethodExpr.Name).ErrorLocs[v.Name]
 		errors = append(errors, &ErrorData{
@@ -1035,7 +1042,7 @@ func buildErrorsData(e *expr.GRPCEndpointExpr, sd *ServiceData) []*ErrorData {
 	return errors
 }
 
-func buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
+func (d *ServicesData) buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
 	// No need to build transformation functions for default error or non-object
 	// types.
 	if ge.Type == expr.ErrorResult || !expr.IsObject(ge.Type) {
@@ -1045,7 +1052,7 @@ func buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd
 	svcCtx := serviceTypeContext(svc.PkgName, svc.Scope)
 	if svr {
 		// server side
-		data := buildInitData(ge.AttributeExpr, ge.Response.Message, "er", "message", svcCtx, true, svr, false, sd)
+		data := d.buildInitData(ge.AttributeExpr, ge.Response.Message, "er", "message", svcCtx, true, svr, false, sd)
 		data.Name = fmt.Sprintf("New%s%sError", codegen.Goify(e.Name(), true), codegen.Goify(ge.Name, true))
 		data.Description = fmt.Sprintf("%s builds the gRPC error response type from the error of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
 		return &ConvertData{
@@ -1058,7 +1065,7 @@ func buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd
 	}
 
 	// client side
-	data := buildInitData(ge.Response.Message, ge.AttributeExpr, "message", "er", svcCtx, false, svr, false, sd)
+	data := d.buildInitData(ge.Response.Message, ge.AttributeExpr, "message", "er", svcCtx, false, svr, false, sd)
 	data.Name = fmt.Sprintf("New%s%sError", codegen.Goify(e.Name(), true), codegen.Goify(ge.Name, true))
 	data.Description = fmt.Sprintf("%s builds the error type of the %q endpoint of the %q service from the gRPC error response type.", data.Name, e.Name(), svc.Name)
 	return &ConvertData{
@@ -1074,7 +1081,7 @@ func buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd
 // buildStreamData builds the StreamData for the server and client streams.
 //
 // svr param indicates that the stream data is built for the server.
-func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *StreamData {
+func (d *ServicesData) buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *StreamData {
 	var (
 		varn                string
 		intName             string
@@ -1117,7 +1124,7 @@ func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *Strea
 				SrcRef:  resCtx.Scope.Ref(result, resCtx.Pkg(result)),
 				TgtName: protoBufGoFullTypeName(e.Response.Message, sd.PkgName, sd.Scope),
 				TgtRef:  protoBufGoFullTypeRef(e.Response.Message, sd.PkgName, sd.Scope),
-				Init:    buildInitData(result, e.Response.Message, resVar, "v", resCtx, true, svr, true, sd),
+				Init:    d.buildInitData(result, e.Response.Message, resVar, "v", resCtx, true, svr, true, sd),
 			}
 		}
 		if e.MethodExpr.StreamingPayload.Type != expr.Empty {
@@ -1129,7 +1136,7 @@ func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *Strea
 				SrcRef:     protoBufGoFullTypeRef(e.StreamingRequest, sd.PkgName, sd.Scope),
 				TgtName:    svcCtx.Scope.Name(e.MethodExpr.StreamingPayload, svcCtx.Pkg(e.MethodExpr.StreamingPayload), svcCtx.Pointer, svcCtx.UseDefault),
 				TgtRef:     recvRef,
-				Init:       buildInitData(e.StreamingRequest, e.MethodExpr.StreamingPayload, "v", "spayload", svcCtx, false, svr, true, sd),
+				Init:       d.buildInitData(e.StreamingRequest, e.MethodExpr.StreamingPayload, "v", "spayload", svcCtx, false, svr, true, sd),
 				Validation: addValidation(e.StreamingRequest, "stream", sd, true),
 			}
 		}
@@ -1148,7 +1155,7 @@ func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *Strea
 				SrcRef:  sendRef,
 				TgtName: protoBufGoFullTypeName(e.StreamingRequest, sd.PkgName, sd.Scope),
 				TgtRef:  protoBufGoFullTypeRef(e.StreamingRequest, sd.PkgName, sd.Scope),
-				Init:    buildInitData(e.MethodExpr.StreamingPayload, e.StreamingRequest, "spayload", "v", svcCtx, true, svr, true, sd),
+				Init:    d.buildInitData(e.MethodExpr.StreamingPayload, e.StreamingRequest, "spayload", "v", svcCtx, true, svr, true, sd),
 			}
 		}
 		if e.MethodExpr.Result.Type != expr.Empty {
@@ -1160,7 +1167,7 @@ func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *Strea
 				SrcRef:     protoBufGoFullTypeRef(e.Response.Message, sd.PkgName, sd.Scope),
 				TgtName:    resCtx.Scope.Name(result, resCtx.Pkg(result), resCtx.Pointer, resCtx.UseDefault),
 				TgtRef:     resCtx.Scope.Ref(result, resCtx.Pkg(result)),
-				Init:       buildInitData(e.Response.Message, result, "v", resVar, resCtx, false, svr, true, sd),
+				Init:       d.buildInitData(e.Response.Message, result, "v", resVar, resCtx, false, svr, true, sd),
 				Validation: addValidation(e.Response.Message, "stream", sd, false),
 			}
 		}
@@ -1198,7 +1205,7 @@ func buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *Strea
 
 // extractMetadata collects the request/response metadata from the given
 // metadata attribute and service type (payload/result).
-func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope) []*MetadataData {
+func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope, services ServicesData) []*MetadataData {
 	var metadata []*MetadataData
 	ctx := serviceTypeContext("", scope)
 	codegen.WalkMappedAttr(a, func(name, elem string, required bool, c *expr.AttributeExpr) error { // nolint: errcheck
@@ -1239,7 +1246,7 @@ func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, s
 				expr.AsArray(mp.ElemType.Type).ElemType.Type.Kind() == expr.StringKind,
 			Validate:     codegen.AttributeValidationCode(c, nil, ctx, required, false, varn, name),
 			DefaultValue: c.DefaultValue,
-			Example:      c.Example(expr.Root.API.ExampleGenerator),
+			Example:      c.Example(services.Root.API.ExampleGenerator),
 		})
 		return nil
 	})
diff --git a/http/codegen/client.go b/http/codegen/client.go
index 14e68654..e500429a 100644
--- a/http/codegen/client.go
+++ b/http/codegen/client.go
@@ -10,19 +10,20 @@ import (
 )
 
 // ClientFiles returns the generated HTTP client files.
-func ClientFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ClientFiles(genpkg string, services *ServicesData) []*codegen.File {
+	root := services.Root
 	var files []*codegen.File
 	for _, svc := range root.API.HTTP.Services {
-		files = append(files, clientFile(genpkg, svc))
-		if f := websocketClientFile(genpkg, svc); f != nil {
+		files = append(files, clientFile(genpkg, svc, services))
+		if f := websocketClientFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
-		if f := sseClientFile(genpkg, svc); f != nil {
+		if f := sseClientFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
 	}
 	for _, svc := range root.API.HTTP.Services {
-		if f := clientEncodeDecodeFile(genpkg, svc); f != nil {
+		if f := clientEncodeDecodeFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
 	}
@@ -30,8 +31,8 @@ func ClientFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 }
 
 // clientFile returns the client HTTP transport file
-func clientFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func clientFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	svcName := data.Service.PathName
 	path := filepath.Join(codegen.Gendir, "http", svcName, "client", "client.go")
 	title := fmt.Sprintf("%s client HTTP transport", svc.Name())
@@ -100,8 +101,8 @@ func clientFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
 
 // clientEncodeDecodeFile returns the file containing the HTTP client encoding
 // and decoding logic.
-func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	svcName := data.Service.PathName
 	path := filepath.Join(codegen.Gendir, "http", svcName, "client", "encode_decode.go")
 	title := fmt.Sprintf("%s HTTP client encoders and decoders", svc.Name())
@@ -140,7 +141,7 @@ func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 					"typeConversionData": typeConversionData,
 					"mapConversionData":  mapConversionData,
 					"goTypeRef": func(dt expr.DataType) string {
-						return service.Services.Get(svc.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
+						return services.ServicesData.Get(svc.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
 					},
 					"isBearer":    isBearer,
 					"aliasedType": fieldType,
@@ -173,7 +174,7 @@ func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 				Data:   e,
 				FuncMap: map[string]any{
 					"goTypeRef": func(dt expr.DataType) string {
-						return service.Services.Get(svc.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
+						return services.ServicesData.Get(svc.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
 					},
 					"buildResponseData": buildResponseData,
 				},
@@ -203,7 +204,7 @@ func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 
 // typeConversionData produces the template data suitable for executing the
 // "header_conversion" template.
-func typeConversionData(dt, ft expr.DataType, varName string, target string) map[string]any {
+func typeConversionData(dt, ft expr.DataType, varName, target string) map[string]any {
 	ut, isut := ft.(expr.UserType)
 	if isut {
 		ft = ut.Attribute().Type
diff --git a/http/codegen/client_cli.go b/http/codegen/client_cli.go
index ad85e35d..4203a4f6 100644
--- a/http/codegen/client_cli.go
+++ b/http/codegen/client_cli.go
@@ -37,7 +37,8 @@ type subcommandData struct {
 }
 
 // ClientCLIFiles returns the client HTTP CLI support file.
-func ClientCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ClientCLIFiles(genpkg string, services *ServicesData) []*codegen.File {
+	root := services.Root
 	if len(root.API.HTTP.Services) == 0 {
 		return nil
 	}
@@ -46,7 +47,7 @@ func ClientCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 		svcs []*expr.HTTPServiceExpr
 	)
 	for _, svc := range root.API.HTTP.Services {
-		sd := HTTPServices.Get(svc.Name())
+		sd := services.Get(svc.Name())
 		if len(sd.Endpoints) > 0 {
 			command := &commandData{
 				CommandData: cli.BuildCommandData(sd.Service),
@@ -75,10 +76,10 @@ func ClientCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 				}
 			}
 		}
-		files = append(files, endpointParser(genpkg, root, svr, svrData))
+		files = append(files, endpointParser(genpkg, root, svr, svrData, services))
 	}
 	for i, svc := range svcs {
-		files = append(files, payloadBuilders(genpkg, svc, data[i].CommandData))
+		files = append(files, payloadBuilders(genpkg, svc, data[i].CommandData, services))
 	}
 	return files
 }
@@ -102,7 +103,7 @@ func buildSubcommandData(sd *ServiceData, e *EndpointData) *subcommandData {
 
 // endpointParser returns the file that implements the command line parser that
 // builds the client endpoint and payload necessary to perform a request.
-func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, data []*commandData) *codegen.File {
+func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, data []*commandData, services *ServicesData) *codegen.File {
 	pkg := codegen.SnakeCase(codegen.Goify(svr.Name, true))
 	path := filepath.Join(codegen.Gendir, "http", "cli", pkg, "cli.go")
 	title := fmt.Sprintf("%s HTTP client CLI support package", svr.Name)
@@ -119,7 +120,7 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
 	}
 	for _, sv := range svr.Services {
 		svc := root.Service(sv)
-		sd := HTTPServices.Get(svc.Name)
+		sd := services.Get(svc.Name)
 		if sd == nil {
 			continue
 		}
@@ -166,8 +167,8 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
 
 // payloadBuilders returns the file that contains the payload constructors that
 // use flag values as arguments.
-func payloadBuilders(genpkg string, svc *expr.HTTPServiceExpr, data *cli.CommandData) *codegen.File {
-	sd := HTTPServices.Get(svc.Name())
+func payloadBuilders(genpkg string, svc *expr.HTTPServiceExpr, data *cli.CommandData, services *ServicesData) *codegen.File {
+	sd := services.Get(svc.Name())
 	path := filepath.Join(codegen.Gendir, "http", sd.Service.PathName, "client", "cli.go")
 	title := fmt.Sprintf("%s HTTP client CLI support package", svc.Name())
 	specs := []*codegen.ImportSpec{
diff --git a/http/codegen/client_types.go b/http/codegen/client_types.go
index a3144369..5bb30f0f 100644
--- a/http/codegen/client_types.go
+++ b/http/codegen/client_types.go
@@ -8,10 +8,11 @@ import (
 )
 
 // ClientTypeFiles returns the HTTP transport client types files.
-func ClientTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ClientTypeFiles(genpkg string, services *ServicesData) []*codegen.File {
+	root := services.Root
 	fw := make([]*codegen.File, len(root.API.HTTP.Services))
 	for i, svc := range root.API.HTTP.Services {
-		fw[i] = clientType(genpkg, svc, make(map[string]struct{}))
+		fw[i] = clientType(genpkg, svc, make(map[string]struct{}), services)
 	}
 	return fw
 }
@@ -40,10 +41,10 @@ func ClientTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 //
 //   - Response header variables hold pointers when not required and have no
 //     default value.
-func clientType(genpkg string, svc *expr.HTTPServiceExpr, seen map[string]struct{}) *codegen.File {
+func clientType(genpkg string, svc *expr.HTTPServiceExpr, seen map[string]struct{}, services *ServicesData) *codegen.File {
 	var (
 		path    string
-		data    = HTTPServices.Get(svc.Name())
+		data    = services.Get(svc.Name())
 		svcName = data.Service.PathName
 	)
 	path = filepath.Join(codegen.Gendir, "http", svcName, "client", "types.go")
diff --git a/http/codegen/example_cli.go b/http/codegen/example_cli.go
index 937f6067..ef72419c 100644
--- a/http/codegen/example_cli.go
+++ b/http/codegen/example_cli.go
@@ -7,16 +7,15 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/example"
-	"goa.design/goa/v3/codegen/service"
 	"goa.design/goa/v3/expr"
 )
 
 // ExampleCLIFiles returns an example client tool HTTP implementation for each
 // server expression.
-func ExampleCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ExampleCLIFiles(genpkg string, services *ServicesData) []*codegen.File {
 	var files []*codegen.File
-	for _, svr := range root.API.Servers {
-		if f := exampleCLI(genpkg, root, svr); f != nil {
+	for _, svr := range services.Root.API.Servers {
+		if f := exampleCLI(genpkg, svr, services); f != nil {
 			files = append(files, f)
 		}
 	}
@@ -25,8 +24,8 @@ func ExampleCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 
 // exampleCLI returns an example client tool HTTP implementation for the given
 // server expression.
-func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
-	svrdata := example.Servers.Get(svr)
+func exampleCLI(genpkg string, svr *expr.ServerExpr, services *ServicesData) *codegen.File {
+	svrdata := example.Servers.Get(svr, services.Root)
 	path := filepath.Join("cmd", svrdata.Dir+"-cli", "http.go")
 	if _, err := os.Stat(path); !os.IsNotExist(err) {
 		return nil // file already exists, skip it.
@@ -52,19 +51,19 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
 		{Path: genpkg + "/http/cli/" + svrdata.Dir, Name: "cli"},
 	}
 	importScope := codegen.NewNameScope()
-	for _, svc := range root.Services {
-		data := service.Services.Get(svc.Name)
+	for _, svc := range services.Root.Services {
+		data := services.ServicesData.Get(svc.Name)
 		specs = append(specs, &codegen.ImportSpec{Path: genpkg + "/" + data.PkgName})
 		importScope.Unique(data.PkgName)
 	}
 	interceptorsPkg := importScope.Unique("interceptors", "ex")
 	specs = append(specs, &codegen.ImportSpec{Path: rootPath + "/interceptors", Name: interceptorsPkg})
-	apiPkg := importScope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
+	apiPkg := importScope.Unique(strings.ToLower(codegen.Goify(services.Root.API.Name, false)), "api")
 	specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: apiPkg})
 
 	var svcData []*ServiceData
 	for _, svc := range svr.Services {
-		if data := HTTPServices.Get(svc); data != nil {
+		if data := services.Get(svc); data != nil {
 			svcData = append(svcData, data)
 		}
 	}
diff --git a/http/codegen/example_server.go b/http/codegen/example_server.go
index 829781bd..f5faccae 100644
--- a/http/codegen/example_server.go
+++ b/http/codegen/example_server.go
@@ -12,15 +12,15 @@ import (
 )
 
 // ExampleServerFiles returns an example http service implementation.
-func ExampleServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ExampleServerFiles(genpkg string, services *ServicesData) []*codegen.File {
 	var fw []*codegen.File
-	for _, svr := range root.API.Servers {
-		if m := exampleServer(genpkg, root, svr); m != nil {
+	for _, svr := range services.Root.API.Servers {
+		if m := exampleServer(genpkg, services.Root, svr, services); m != nil {
 			fw = append(fw, m)
 		}
 	}
-	for _, svc := range root.API.HTTP.Services {
-		if f := dummyMultipartFile(genpkg, root, svc); f != nil {
+	for _, svc := range services.Root.API.HTTP.Services {
+		if f := dummyMultipartFile(genpkg, services.Root, svc, services); f != nil {
 			fw = append(fw, f)
 		}
 	}
@@ -28,8 +28,8 @@ func ExampleServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 }
 
 // exampleServer returns an example HTTP server implementation.
-func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
-	svrdata := example.Servers.Get(svr)
+func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, services *ServicesData) *codegen.File {
+	svrdata := example.Servers.Get(svr, root)
 	fpath := filepath.Join("cmd", svrdata.Dir, "http.go")
 	specs := []*codegen.ImportSpec{
 		{Path: "context"},
@@ -47,16 +47,17 @@ func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *co
 
 	scope := codegen.NewNameScope()
 	for _, svc := range root.API.HTTP.Services {
-		sd := HTTPServices.Get(svc.Name())
+		sd := services.Get(svc.Name())
 		svcName := sd.Service.PathName
-		specs = append(specs, &codegen.ImportSpec{
-			Path: path.Join(genpkg, "http", svcName, "server"),
-			Name: scope.Unique(sd.Service.PkgName + "svr"),
-		})
-		specs = append(specs, &codegen.ImportSpec{
-			Path: path.Join(genpkg, svcName),
-			Name: scope.Unique(sd.Service.PkgName),
-		})
+		specs = append(specs,
+			&codegen.ImportSpec{
+				Path: path.Join(genpkg, "http", svcName, "server"),
+				Name: scope.Unique(sd.Service.PkgName + "svr"),
+			},
+			&codegen.ImportSpec{
+				Path: path.Join(genpkg, svcName),
+				Name: scope.Unique(sd.Service.PkgName),
+			})
 	}
 
 	var (
@@ -70,13 +71,13 @@ func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *co
 		if idx > 0 {
 			rootPath = genpkg[:idx]
 		}
-		apiPkg = scope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
+		apiPkg = scope.Unique(strings.ToLower(codegen.Goify(services.Root.API.Name, false)), "api")
 	}
 	specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: apiPkg})
 
 	var svcdata []*ServiceData
 	for _, svc := range svr.Services {
-		if data := HTTPServices.Get(svc); data != nil {
+		if data := services.Get(svc); data != nil {
 			svcdata = append(svcdata, data)
 		}
 	}
@@ -129,7 +130,7 @@ func exampleServer(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *co
 
 // dummyMultipartFile returns a dummy implementation of the multipart decoders
 // and encoders.
-func dummyMultipartFile(genpkg string, root *expr.RootExpr, svc *expr.HTTPServiceExpr) *codegen.File {
+func dummyMultipartFile(genpkg string, root *expr.RootExpr, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
 	mpath := "multipart.go"
 	if _, err := os.Stat(mpath); !os.IsNotExist(err) {
 		return nil // file already exists, skip it.
@@ -142,7 +143,7 @@ func dummyMultipartFile(genpkg string, root *expr.RootExpr, svc *expr.HTTPServic
 	)
 	// determine the unique API package name different from the service names
 	for _, svc := range root.Services {
-		s := HTTPServices.Get(svc.Name)
+		s := services.Get(svc.Name)
 		if s == nil {
 			panic("unknown http service, " + svc.Name) // bug
 		}
@@ -155,7 +156,7 @@ func dummyMultipartFile(genpkg string, root *expr.RootExpr, svc *expr.HTTPServic
 		specs := []*codegen.ImportSpec{
 			{Path: "mime/multipart"},
 		}
-		data := HTTPServices.Get(svc.Name())
+		data := services.Get(svc.Name())
 		specs = append(specs, &codegen.ImportSpec{
 			Path: path.Join(genpkg, data.Service.PathName),
 			Name: scope.Unique(data.Service.PkgName, "svc"),
diff --git a/http/codegen/openapi/v2/builder.go b/http/codegen/openapi/v2/builder.go
index fd6f2fe0..d6e73920 100644
--- a/http/codegen/openapi/v2/builder.go
+++ b/http/codegen/openapi/v2/builder.go
@@ -234,7 +234,7 @@ func hasAbsoluteRoutes(root *expr.RootExpr) bool {
 	return hasAbsoluteRoutes
 }
 
-func summaryFromExpr(name string, e *expr.HTTPEndpointExpr) string {
+func summaryFromExpr(name string, e *expr.HTTPEndpointExpr, meta expr.MetaExpr) string {
 	for n, mdata := range e.Meta {
 		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 			return mdata[0]
@@ -250,7 +250,7 @@ func summaryFromExpr(name string, e *expr.HTTPEndpointExpr) string {
 			return mdata[0]
 		}
 	}
-	for n, mdata := range expr.Root.API.Meta {
+	for n, mdata := range meta {
 		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 			return mdata[0]
 		}
@@ -625,7 +625,7 @@ func buildPathFromExpr(s *V2, root *expr.RootExpr, h *expr.HostExpr, route *expr
 		operation := &Operation{
 			Tags:         tagNames,
 			Description:  description,
-			Summary:      summaryFromExpr(endpoint.Name()+" "+endpoint.Service.Name(), endpoint),
+			Summary:      summaryFromExpr(endpoint.Name()+" "+endpoint.Service.Name(), endpoint, root.API.Meta),
 			ExternalDocs: openapi.DocsFromExpr(endpoint.MethodExpr.Docs, endpoint.MethodExpr.Meta),
 			OperationID:  operationID,
 			Parameters:   params,
@@ -724,16 +724,16 @@ func initExclusiveMinimumValidation(def any, exclMin *float64) {
 	}
 }
 
-func initMinimumValidation(def any, min *float64) {
+func initMinimumValidation(def any, minimum *float64) {
 	switch actual := def.(type) {
 	case *Parameter:
-		actual.Minimum = min
+		actual.Minimum = minimum
 		actual.ExclusiveMinimum = false
 	case *Header:
-		actual.Minimum = min
+		actual.Minimum = minimum
 		actual.ExclusiveMinimum = false
 	case *Items:
-		actual.Minimum = min
+		actual.Minimum = minimum
 		actual.ExclusiveMinimum = false
 	}
 }
@@ -752,47 +752,47 @@ func initExclusiveMaximumValidation(def any, exclMax *float64) {
 	}
 }
 
-func initMaximumValidation(def any, max *float64) {
+func initMaximumValidation(def any, maximum *float64) {
 	switch actual := def.(type) {
 	case *Parameter:
-		actual.Maximum = max
+		actual.Maximum = maximum
 		actual.ExclusiveMaximum = false
 	case *Header:
-		actual.Maximum = max
+		actual.Maximum = maximum
 		actual.ExclusiveMaximum = false
 	case *Items:
-		actual.Maximum = max
+		actual.Maximum = maximum
 		actual.ExclusiveMaximum = false
 	}
 }
 
-func initMinLengthValidation(def any, isArray bool, min *int) {
+func initMinLengthValidation(def any, isArray bool, minLength *int) {
 	switch actual := def.(type) {
 	case *Parameter:
 		if isArray {
-			actual.MinItems = min
+			actual.MinItems = minLength
 		} else {
-			actual.MinLength = min
+			actual.MinLength = minLength
 		}
 	case *Header:
-		actual.MinLength = min
+		actual.MinLength = minLength
 	case *Items:
-		actual.MinLength = min
+		actual.MinLength = minLength
 	}
 }
 
-func initMaxLengthValidation(def any, isArray bool, max *int) {
+func initMaxLengthValidation(def any, isArray bool, maxLength *int) {
 	switch actual := def.(type) {
 	case *Parameter:
 		if isArray {
-			actual.MaxItems = max
+			actual.MaxItems = maxLength
 		} else {
-			actual.MaxLength = max
+			actual.MaxLength = maxLength
 		}
 	case *Header:
-		actual.MaxLength = max
+		actual.MaxLength = maxLength
 	case *Items:
-		actual.MaxLength = max
+		actual.MaxLength = maxLength
 	}
 }
 
diff --git a/http/codegen/openapi/v3/builder.go b/http/codegen/openapi/v3/builder.go
index 15e1a0d9..e2bb3922 100644
--- a/http/codegen/openapi/v3/builder.go
+++ b/http/codegen/openapi/v3/builder.go
@@ -39,7 +39,7 @@ func New(root *expr.RootExpr) *OpenAPI {
 	}
 
 	var (
-		bodies, types = buildBodyTypes(root.API)
+		bodies, types = buildBodyTypes(root.API, root.Types, root.ResultTypes)
 
 		info     = buildInfo(root.API)
 		comps    = buildComponents(root, types)
@@ -136,7 +136,7 @@ func buildPaths(h *expr.HTTPExpr, bodies map[string]map[string]*EndpointBodies,
 					// Remove any wildcards that is defined in path as a workaround to
 					// https://github.com/OAI/OpenAPI-Specification/issues/291
 					key = expr.HTTPWildcardRegex.ReplaceAllString(key, "/{$1}")
-					operation := buildOperation(key, r, sbod[e.Name()], api.ExampleGenerator)
+					operation := buildOperation(key, r, sbod[e.Name()], api.ExampleGenerator, api.Meta)
 					path, ok := paths[key]
 					if !ok {
 						path = new(PathItem)
@@ -190,7 +190,7 @@ func buildPaths(h *expr.HTTPExpr, bodies map[string]map[string]*EndpointBodies,
 }
 
 // buildOperation builds the OpenAPI Operation object for the given path.
-func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand *expr.ExampleGenerator) *Operation {
+func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand *expr.ExampleGenerator, meta expr.MetaExpr) *Operation {
 	e := r.Endpoint
 	m := e.MethodExpr
 	svc := e.Service
@@ -209,13 +209,11 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 		}
 	}
 
-	{
-		summary = fmt.Sprintf("%s %s", e.Name(), svc.Name())
-		setSummary(expr.Root.API.Meta)
-		setSummary(svc.ServiceExpr.Meta)
-		setSummary(r.Endpoint.Meta)
-		setSummary(m.Meta)
-	}
+	summary = fmt.Sprintf("%s %s", e.Name(), svc.Name())
+	setSummary(meta)
+	setSummary(svc.ServiceExpr.Meta)
+	setSummary(r.Endpoint.Meta)
+	setSummary(m.Meta)
 
 	// OpenAPI operationId
 	var operationIDFormat string
@@ -227,13 +225,11 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 		}
 	}
 
-	{
-		operationIDFormat = defaultOperationIDFormat
-		setOperationIDFormat(expr.Root.API.Meta)
-		setOperationIDFormat(m.Service.Meta)
-		setOperationIDFormat(r.Endpoint.Meta)
-		setOperationIDFormat(m.Meta)
-	}
+	operationIDFormat = defaultOperationIDFormat
+	setOperationIDFormat(meta)
+	setOperationIDFormat(m.Service.Meta)
+	setOperationIDFormat(r.Endpoint.Meta)
+	setOperationIDFormat(m.Meta)
 
 	// request body
 	var requestBody *RequestBodyRef
@@ -281,52 +277,47 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 	}
 
 	// responses
-	var responses map[string]*ResponseRef
-	{
-		responses = make(map[string]*ResponseRef, len(e.Responses))
-		for _, r := range e.Responses {
-			if e.MethodExpr.IsStreaming() {
-				// A streaming endpoint allows at most one successful response
-				// definition. So it is okay to change the first successful
-				// response to a HTTP 101 response for openapi docs.
-				if _, ok := responses[strconv.Itoa(expr.StatusSwitchingProtocols)]; !ok {
-					b := bodies.ResponseBodies[r.StatusCode]
-					delete(bodies.ResponseBodies, r.StatusCode)
-					r = r.Dup()
-					r.StatusCode = expr.StatusSwitchingProtocols
-					bodies.ResponseBodies[r.StatusCode] = b
-				}
+	responses := make(map[string]*ResponseRef, len(e.Responses))
+	for _, r := range e.Responses {
+		if e.MethodExpr.IsStreaming() {
+			// A streaming endpoint allows at most one successful response
+			// definition. So it is okay to change the first successful
+			// response to a HTTP 101 response for openapi docs.
+			if _, ok := responses[strconv.Itoa(expr.StatusSwitchingProtocols)]; !ok {
+				b := bodies.ResponseBodies[r.StatusCode]
+				delete(bodies.ResponseBodies, r.StatusCode)
+				r = r.Dup()
+				r.StatusCode = expr.StatusSwitchingProtocols
+				bodies.ResponseBodies[r.StatusCode] = b
 			}
-			resp := responseFromExpr(r, bodies.ResponseBodies, rand)
-			responses[strconv.Itoa(r.StatusCode)] = &ResponseRef{Value: resp}
 		}
-		for _, er := range e.HTTPErrors {
-			if er.Description != "" && er.Response.Description == "" {
-				er.Response.Description = er.Description
-			}
-			resp := responseFromExpr(er.Response, bodies.ResponseBodies, rand)
-			desc := er.Name
-			if resp.Description != nil {
-				desc += ": " + *resp.Description
-			}
-			resp.Description = &desc
-			if er.Type == expr.ErrorResult && len(er.Response.Body.ExtractUserExamples()) == 0 {
-				for _, content := range resp.Content {
-					content.Example = nil
-				}
+		resp := responseFromExpr(r, bodies.ResponseBodies, rand)
+		responses[strconv.Itoa(r.StatusCode)] = &ResponseRef{Value: resp}
+	}
+	for _, er := range e.HTTPErrors {
+		if er.Description != "" && er.Response.Description == "" {
+			er.Response.Description = er.Description
+		}
+		resp := responseFromExpr(er.Response, bodies.ResponseBodies, rand)
+		desc := er.Name
+		if resp.Description != nil {
+			desc += ": " + *resp.Description
+		}
+		resp.Description = &desc
+		if er.Type == expr.ErrorResult && len(er.Response.Body.ExtractUserExamples()) == 0 {
+			for _, content := range resp.Content {
+				content.Example = nil
 			}
-			responses[strconv.Itoa(er.Response.StatusCode)] = &ResponseRef{Value: resp}
 		}
+		responses[strconv.Itoa(er.Response.StatusCode)] = &ResponseRef{Value: resp}
 	}
 
 	// tag names
 	var tagNames []string
-	{
-		tagNames = openapi.TagNamesFromExpr(e.Meta)
-		if len(tagNames) == 0 {
-			// By default tag with service name
-			tagNames = []string{r.Endpoint.Service.Name()}
-		}
+	tagNames = openapi.TagNamesFromExpr(e.Meta)
+	if len(tagNames) == 0 {
+		// By default tag with service name
+		tagNames = []string{r.Endpoint.Service.Name()}
 	}
 
 	// An endpoint can have multiple routes, so we need to be able to build a unique
@@ -363,18 +354,16 @@ func buildFileServerOperation(key string, fs *expr.HTTPFileServerExpr, api *expr
 
 	// parameters
 	var params []*ParameterRef
-	{
-		if len(wildcards) > 0 {
-			pref := ParameterRef{
-				Value: &Parameter{
-					Name:        wildcards[0],
-					Description: "Relative file path",
-					In:          "path",
-					Required:    true,
-				},
-			}
-			params = []*ParameterRef{&pref}
+	if len(wildcards) > 0 {
+		pref := ParameterRef{
+			Value: &Parameter{
+				Name:        wildcards[0],
+				Description: "Relative file path",
+				In:          "path",
+				Required:    true,
+			},
 		}
+		params = []*ParameterRef{&pref}
 	}
 
 	// responses
@@ -401,12 +390,10 @@ func buildFileServerOperation(key string, fs *expr.HTTPFileServerExpr, api *expr
 
 	// OpenAPI summary
 	var summary string
-	{
-		summary = fmt.Sprintf("Download %s", fs.FilePath)
-		for n, mdata := range fs.Meta {
-			if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
-				summary = mdata[0]
-			}
+	summary = fmt.Sprintf("Download %s", fs.FilePath)
+	for n, mdata := range fs.Meta {
+		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
+			summary = mdata[0]
 		}
 	}
 
@@ -420,21 +407,17 @@ func buildFileServerOperation(key string, fs *expr.HTTPFileServerExpr, api *expr
 		}
 	}
 
-	{
-		operationIDFormat = defaultOperationIDFormat
-		setOperationIDFormat(api.Meta)
-		setOperationIDFormat(svc.Meta)
-		setOperationIDFormat(fs.Meta)
-	}
+	operationIDFormat = defaultOperationIDFormat
+	setOperationIDFormat(api.Meta)
+	setOperationIDFormat(svc.Meta)
+	setOperationIDFormat(fs.Meta)
 
 	// tag names
 	var tagNames []string
-	{
-		tagNames = openapi.TagNamesFromExpr(fs.Meta)
-		if len(tagNames) == 0 {
-			// By default tag with service name
-			tagNames = []string{svc.Name()}
-		}
+	tagNames = openapi.TagNamesFromExpr(fs.Meta)
+	if len(tagNames) == 0 {
+		// By default tag with service name
+		tagNames = []string{svc.Name()}
 	}
 
 	return &Operation{
@@ -659,23 +642,21 @@ func buildTags(api *expr.APIExpr) []*openapi.Tag {
 	sort.Strings(keys)
 
 	var tags []*openapi.Tag
-	{
-		for _, k := range keys {
-			tags = append(tags, m[k])
-		}
+	for _, k := range keys {
+		tags = append(tags, m[k])
+	}
 
-		if len(tags) == 0 {
-			// add service name and description to the tags since we tag every
-			// operation with service name when no custom tag is defined
-			for _, s := range api.HTTP.Services {
-				if !openapi.MustGenerate(s.Meta) || !openapi.MustGenerate(s.ServiceExpr.Meta) {
-					continue
-				}
-				tags = append(tags, &openapi.Tag{
-					Name:        s.Name(),
-					Description: s.Description(),
-				})
+	if len(tags) == 0 {
+		// add service name and description to the tags since we tag every
+		// operation with service name when no custom tag is defined
+		for _, s := range api.HTTP.Services {
+			if !openapi.MustGenerate(s.Meta) || !openapi.MustGenerate(s.ServiceExpr.Meta) {
+				continue
 			}
+			tags = append(tags, &openapi.Tag{
+				Name:        s.Name(),
+				Description: s.Description(),
+			})
 		}
 	}
 	return tags
diff --git a/http/codegen/openapi/v3/types.go b/http/codegen/openapi/v3/types.go
index be232d49..cd758d98 100644
--- a/http/codegen/openapi/v3/types.go
+++ b/http/codegen/openapi/v3/types.go
@@ -61,18 +61,18 @@ func newSchemafier(rand *expr.ExampleGenerator) *schemafier {
 // value indexed by type name.
 //
 // NOTE: entries are nil when the corresponding type is Empty.
-func buildBodyTypes(api *expr.APIExpr) (map[string]map[string]*EndpointBodies, map[string]*openapi.Schema) {
+func buildBodyTypes(api *expr.APIExpr, types []expr.UserType, resultTypes []*expr.ResultTypeExpr) (map[string]map[string]*EndpointBodies, map[string]*openapi.Schema) {
 	bodies := make(map[string]map[string]*EndpointBodies)
 	sf := newSchemafier(api.ExampleGenerator)
 
 	// Generates the types referenced from the endpoints.
-	for _, t := range expr.Root.Types {
+	for _, t := range types {
 		if !mustGenerateType(t.Attribute().Meta) {
 			continue
 		}
 		sf.schemafy(&expr.AttributeExpr{Type: t})
 	}
-	for _, t := range expr.Root.ResultTypes {
+	for _, t := range resultTypes {
 		if !mustGenerateType(t.Attribute().Meta) {
 			continue
 		}
diff --git a/http/codegen/paths.go b/http/codegen/paths.go
index 238c5a10..cc328186 100644
--- a/http/codegen/paths.go
+++ b/http/codegen/paths.go
@@ -9,34 +9,35 @@ import (
 )
 
 // PathFiles returns the service path files.
-func PathFiles(root *expr.RootExpr) []*codegen.File {
+func PathFiles(services *ServicesData) []*codegen.File {
+	root := services.Root
 	fw := make([]*codegen.File, 2*len(root.API.HTTP.Services))
 	for i := 0; i < len(root.API.HTTP.Services); i++ {
-		fw[i*2] = serverPath(root.API.HTTP.Services[i])
-		fw[i*2+1] = clientPath(root.API.HTTP.Services[i])
+		fw[i*2] = serverPath(root.API.HTTP.Services[i], services)
+		fw[i*2+1] = clientPath(root.API.HTTP.Services[i], services)
 	}
 	return fw
 }
 
 // serverPath returns the server file containing the request path constructors
 // for the given service.
-func serverPath(svc *expr.HTTPServiceExpr) *codegen.File {
-	sd := HTTPServices.Get(svc.Name())
+func serverPath(svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	sd := services.Get(svc.Name())
 	path := filepath.Join(codegen.Gendir, "http", sd.Service.PathName, "server", "paths.go")
-	return &codegen.File{Path: path, SectionTemplates: pathSections(svc, "server")}
+	return &codegen.File{Path: path, SectionTemplates: pathSections(svc, "server", services)}
 }
 
 // clientPath returns the client file containing the request path constructors
 // for the given service.
-func clientPath(svc *expr.HTTPServiceExpr) *codegen.File {
-	sd := HTTPServices.Get(svc.Name())
+func clientPath(svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	sd := services.Get(svc.Name())
 	path := filepath.Join(codegen.Gendir, "http", sd.Service.PathName, "client", "paths.go")
-	return &codegen.File{Path: path, SectionTemplates: pathSections(svc, "client")}
+	return &codegen.File{Path: path, SectionTemplates: pathSections(svc, "client", services)}
 }
 
 // pathSections returns the sections of the file of the pkg package that
 // contains the request path constructors for the given service.
-func pathSections(svc *expr.HTTPServiceExpr, pkg string) []*codegen.SectionTemplate {
+func pathSections(svc *expr.HTTPServiceExpr, pkg string, services *ServicesData) []*codegen.SectionTemplate {
 	title := fmt.Sprintf("HTTP request path constructors for the %s service.", svc.Name())
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, pkg, []*codegen.ImportSpec{
@@ -46,7 +47,7 @@ func pathSections(svc *expr.HTTPServiceExpr, pkg string) []*codegen.SectionTempl
 			{Path: "strings"},
 		}),
 	}
-	sdata := HTTPServices.Get(svc.Name())
+	sdata := services.Get(svc.Name())
 	for _, e := range svc.HTTPEndpoints {
 		sections = append(sections, &codegen.SectionTemplate{
 			Name:   "path",
diff --git a/http/codegen/server.go b/http/codegen/server.go
index 93d8428d..524a346e 100644
--- a/http/codegen/server.go
+++ b/http/codegen/server.go
@@ -8,24 +8,24 @@ import (
 	"strings"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/codegen/service"
 	"goa.design/goa/v3/expr"
 )
 
 // ServerFiles returns the generated HTTP server files.
-func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ServerFiles(genpkg string, services *ServicesData) []*codegen.File {
+	root := services.Root
 	var files []*codegen.File
 	for _, svc := range root.API.HTTP.Services {
-		files = append(files, serverFile(genpkg, svc))
-		if f := websocketServerFile(genpkg, svc); f != nil {
+		files = append(files, serverFile(genpkg, svc, services))
+		if f := websocketServerFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
-		if f := sseServerFile(genpkg, svc); f != nil {
+		if f := sseServerFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
 	}
 	for _, svc := range root.API.HTTP.Services {
-		if f := serverEncodeDecodeFile(genpkg, svc); f != nil {
+		if f := serverEncodeDecodeFile(genpkg, svc, services); f != nil {
 			files = append(files, f)
 		}
 	}
@@ -33,8 +33,8 @@ func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 }
 
 // server returns the file implementing the HTTP server.
-func serverFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func serverFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	svcName := data.Service.PathName
 	fpath := filepath.Join(codegen.Gendir, "http", svcName, "server", "server.go")
 	title := fmt.Sprintf("%s HTTP server", svc.Name())
@@ -119,8 +119,8 @@ func serverFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
 
 // serverEncodeDecodeFile returns the file defining the HTTP server encoding and
 // decoding logic.
-func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	svcName := data.Service.PathName
 	path := filepath.Join(codegen.Gendir, "http", svcName, "server", "encode_decode.go")
 	title := fmt.Sprintf("%s HTTP server encoders and decoders", svc.Name())
@@ -147,13 +147,13 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 		if e.Redirect == nil && !isWebSocketEndpoint(e) {
 			sections = append(sections, &codegen.SectionTemplate{
 				Name:    "response-encoder",
-				FuncMap: transTmplFuncs(svc),
+				FuncMap: transTmplFuncs(svc, services),
 				Source:  readTemplate("response_encoder", "response", "header_conversion"),
 				Data:    e,
 			})
 		}
 		if mustDecodeRequest(e) {
-			fm := transTmplFuncs(svc)
+			fm := transTmplFuncs(svc, services)
 			fm["mapQueryDecodeData"] = mapQueryDecodeData
 			sections = append(sections, &codegen.SectionTemplate{
 				Name:    "request-decoder",
@@ -163,7 +163,7 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 			})
 		}
 		if e.MultipartRequestDecoder != nil {
-			fm := transTmplFuncs(svc)
+			fm := transTmplFuncs(svc, services)
 			fm["mapQueryDecodeData"] = mapQueryDecodeData
 			sections = append(sections, &codegen.SectionTemplate{
 				Name:    "multipart-request-decoder",
@@ -176,7 +176,7 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 			sections = append(sections, &codegen.SectionTemplate{
 				Name:    "error-encoder",
 				Source:  readTemplate("error_encoder", "response", "header_conversion"),
-				FuncMap: transTmplFuncs(svc),
+				FuncMap: transTmplFuncs(svc, services),
 				Data:    e,
 			})
 		}
@@ -198,10 +198,10 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 	return &codegen.File{Path: path, SectionTemplates: sections}
 }
 
-func transTmplFuncs(s *expr.HTTPServiceExpr) map[string]any {
+func transTmplFuncs(s *expr.HTTPServiceExpr, services *ServicesData) map[string]any {
 	return map[string]any{
 		"goTypeRef": func(dt expr.DataType) string {
-			return service.Services.Get(s.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
+			return services.ServicesData.Get(s.Name()).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
 		},
 		"isAliased": func(dt expr.DataType) bool {
 			_, ok := dt.(expr.UserType)
diff --git a/http/codegen/server_types.go b/http/codegen/server_types.go
index a5ccca54..351d2b1f 100644
--- a/http/codegen/server_types.go
+++ b/http/codegen/server_types.go
@@ -8,11 +8,12 @@ import (
 )
 
 // ServerTypeFiles returns the HTTP transport type files.
-func ServerTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
+func ServerTypeFiles(genpkg string, services *ServicesData) []*codegen.File {
+	root := services.Root
 	fw := make([]*codegen.File, len(root.API.HTTP.Services))
 	seen := make(map[string]struct{})
 	for i, r := range root.API.HTTP.Services {
-		fw[i] = serverType(genpkg, r, seen)
+		fw[i] = serverType(genpkg, r, seen, services)
 	}
 	return fw
 }
@@ -41,10 +42,10 @@ func ServerTypeFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
 //
 //   - Response body fields (if the body is a struct) and header variables hold
 //     pointers when not required and have no default value.
-func serverType(genpkg string, svc *expr.HTTPServiceExpr, _ map[string]struct{}) *codegen.File {
+func serverType(genpkg string, svc *expr.HTTPServiceExpr, _ map[string]struct{}, services *ServicesData) *codegen.File {
 	var (
 		path    string
-		data    = HTTPServices.Get(svc.Name())
+		data    = services.Get(svc.Name())
 		svcName = data.Service.PathName
 	)
 	path = filepath.Join(codegen.Gendir, "http", svcName, "server", "types.go")
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index 2d8bf0f0..c437ebc1 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -15,10 +15,6 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// HTTPServices holds the data computed from the design needed to generate the
-// transport code of the services.
-var HTTPServices = make(ServicesData)
-
 var (
 	// pathInitTmpl is the template used to render path constructors code.
 	pathInitTmpl = template.Must(
@@ -30,22 +26,19 @@ var (
 	requestInitTmpl = template.Must(
 		template.New("request-init").
 			Funcs(template.FuncMap{
-				"goTypeRef": func(dt expr.DataType, svc string) string {
-					return service.Services.Get(svc).Scope.GoTypeRef(&expr.AttributeExpr{Type: dt})
-				},
-				"isAliased": func(dt expr.DataType) bool {
-					_, ok := dt.(expr.UserType)
-					return ok
-				},
 				"isWebSocketEndpoint": isWebSocketEndpoint,
 			}).
 			Parse(readTemplate("request_init")),
 	)
 )
 
+
 type (
 	// ServicesData encapsulates the data computed from the design.
-	ServicesData map[string]*ServiceData
+	ServicesData struct {
+		*service.ServicesData
+		HTTPServices map[string]*ServiceData
+	}
 
 	// ServiceData contains the data used to render the code related to a
 	// single service.
@@ -449,6 +442,10 @@ type (
 		Validate string
 		// Example is an example attribute value
 		Example any
+		// IsAliased is true if the field type is a user-defined type (alias).
+		IsAliased bool
+		// ServiceTypeRef is the service-aware type reference for cross-service resolution.
+		ServiceTypeRef string
 	}
 
 	// InitArgData represents a single constructor argument.
@@ -567,19 +564,27 @@ type (
 	}
 )
 
+// NewServicesData creates a new ServicesData instance for the given service data.
+func NewServicesData(services *service.ServicesData) *ServicesData {
+	return &ServicesData{
+		ServicesData: services,
+		HTTPServices: make(map[string]*ServiceData),
+	}
+}
+
 // Get retrieves the transport data for the service with the given name
 // computing it if needed. It returns nil if there is no service with the given
 // name.
-func (d ServicesData) Get(name string) *ServiceData {
-	if data, ok := d[name]; ok {
+func (sds *ServicesData) Get(name string) *ServiceData {
+	if data, ok := sds.HTTPServices[name]; ok {
 		return data
 	}
-	service := expr.Root.API.HTTP.Service(name)
-	if service == nil {
+	httpService := sds.Root.API.HTTP.Service(name)
+	if httpService == nil {
 		return nil
 	}
-	d[name] = d.analyze(service)
-	return d[name]
+	sds.HTTPServices[name] = sds.analyze(httpService)
+	return sds.HTTPServices[name]
 }
 
 // Endpoint returns the service method transport data for the endpoint with the
@@ -595,8 +600,8 @@ func (svc *ServiceData) Endpoint(name string) *EndpointData {
 
 // analyze creates the data necessary to render the code of the given service.
 // It records the user types needed by the service definition in userTypes.
-func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
-	svc := service.Services.Get(httpSvc.ServiceExpr.Name)
+func (sds *ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
+	svc := sds.ServicesData.Get(httpSvc.ServiceExpr.Name)
 	scope := codegen.NewNameScope()
 	scope.Unique("c") // 'c' is reserved as the client's receiver name.
 	scope.Unique("v") // 'v' is reserved as the request builder payload argument name.
@@ -696,7 +701,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 								Type:        att.Type,
 								Pointer:     pointer,
 								Required:    true,
-								Example:     att.Example(expr.Root.API.ExampleGenerator),
+								Example:     att.Example(sds.Root.API.ExampleGenerator),
 								Validate:    vcode,
 							},
 						}
@@ -732,7 +737,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 			}
 		}
 
-		payload := buildPayloadData(httpEndpoint, sd)
+		payload := sds.buildPayloadData(httpEndpoint, sd)
 
 		var (
 			reqs  service.RequirementsData
@@ -783,6 +788,15 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 				if ca.FieldName != "" {
 					ca.VarName = s.Unique(ca.VarName)
 					ca.Ref = ca.VarName
+					// Populate service-aware type resolution fields
+					_, ca.IsAliased = ca.FieldType.(expr.UserType)
+					if ca.IsAliased {
+						if svcData := sds.ServicesData.Get(svc.Name); svcData != nil {
+							ca.ServiceTypeRef = svcData.Scope.GoTypeRef(&expr.AttributeExpr{Type: ca.FieldType})
+						} else {
+							ca.ServiceTypeRef = codegen.Goify(ca.FieldType.Name(), true)
+						}
+					}
 					args = append(args, ca)
 				}
 			}
@@ -822,8 +836,8 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 			ServiceVarName:  svc.VarName,
 			ServicePkgName:  svc.PkgName,
 			Payload:         payload,
-			Result:          buildResultData(httpEndpoint, sd),
-			Errors:          buildErrorsData(httpEndpoint, sd),
+			Result:          sds.buildResultData(httpEndpoint, sd),
+			Errors:          sds.buildErrorsData(httpEndpoint, sd),
 			HeaderSchemes:   hsch,
 			BodySchemes:     bosch,
 			QuerySchemes:    qsch,
@@ -842,7 +856,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 			Requirements:    reqs,
 		}
 		if httpEndpoint.MethodExpr.IsStreaming() {
-			initWebSocketData(ed, httpEndpoint, sd)
+			sds.initWebSocketData(ed, httpEndpoint, sd)
 			initSSEData(ed, httpEndpoint, sd)
 		}
 
@@ -881,20 +895,20 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 
 	for _, a := range httpSvc.HTTPEndpoints {
 		collectUserTypes(a.Body.Type, func(ut expr.UserType) {
-			if d := attributeTypeData(ut, true, true, true, sd); d != nil {
+			if d := sds.attributeTypeData(ut, true, true, true, sd); d != nil {
 				sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d)
 			}
-			if d := attributeTypeData(ut, true, false, false, sd); d != nil {
+			if d := sds.attributeTypeData(ut, true, false, false, sd); d != nil {
 				sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
 			}
 		})
 
 		if a.MethodExpr.StreamingPayload.Type != expr.Empty {
 			collectUserTypes(a.StreamingBody.Type, func(ut expr.UserType) {
-				if d := attributeTypeData(ut, true, true, true, sd); d != nil {
+				if d := sds.attributeTypeData(ut, true, true, true, sd); d != nil {
 					sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d)
 				}
-				if d := attributeTypeData(ut, true, false, false, sd); d != nil {
+				if d := sds.attributeTypeData(ut, true, false, false, sd); d != nil {
 					sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
 				}
 			})
@@ -906,7 +920,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 					// NOTE: ServerBodyAttributeTypes for response body types are
 					// collected in buildResponseBodyType because we have to generate
 					// body types for each view in a result type.
-					if d := attributeTypeData(ut, false, true, false, sd); d != nil {
+					if d := sds.attributeTypeData(ut, false, true, false, sd); d != nil {
 						sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
 					}
 				})
@@ -918,7 +932,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
 				// NOTE: ServerBodyAttributeTypes for error response body types are
 				// collected in buildResponseBodyType because we have to generate
 				// body types for each view in a result type.
-				if d := attributeTypeData(ut, false, true, false, sd); d != nil {
+				if d := sds.attributeTypeData(ut, false, true, false, sd); d != nil {
 					sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
 				}
 			})
@@ -982,7 +996,7 @@ func makeHTTPTypeRecursive(att *expr.AttributeExpr, seen map[string]struct{}) *e
 // buildPayloadData returns the data structure used to describe the endpoint
 // payload including the HTTP request details. It also returns the user types
 // used by the request body type recursively if any.
-func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
+func (sds *ServicesData) buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 	e.Body = makeHTTPType(e.Body)
 	var (
 		payload    = e.MethodExpr.Payload
@@ -999,12 +1013,12 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 	)
 	{
 		var (
-			serverBodyData = buildRequestBodyType(e.Body, payload, e, true, sd)
-			clientBodyData = buildRequestBodyType(e.Body, payload, e, false, sd)
-			paramsData     = extractPathParams(e.PathParams(), payload, sd.Scope)
-			queryData      = extractQueryParams(e.QueryParams(), payload, sd.Scope)
-			headersData    = extractHeaders(e.Headers, payload, svcctx, sd.Scope)
-			cookiesData    = extractCookies(e.Cookies, payload, svcctx, sd.Scope)
+			serverBodyData = sds.buildRequestBodyType(e.Body, payload, e, true, sd)
+			clientBodyData = sds.buildRequestBodyType(e.Body, payload, e, false, sd)
+			paramsData     = sds.extractPathParams(e.PathParams(), payload, sd.Scope)
+			queryData      = sds.extractQueryParams(e.QueryParams(), payload, sd.Scope)
+			headersData    = sds.extractHeaders(e.Headers, payload, svcctx, sd.Scope)
+			cookiesData    = sds.extractCookies(e.Cookies, payload, svcctx, sd.Scope)
 			origin         string
 
 			mustValidate bool
@@ -1040,7 +1054,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 						TypeRef:      sd.Scope.GoTypeRef(pAtt),
 						Validate:     codegen.AttributeValidationCode(pAtt, nil, httpsvrctx, required, expr.IsAlias(pAtt.Type), varn, name),
 						DefaultValue: pAtt.DefaultValue,
-						Example:      pAtt.Example(expr.Root.API.ExampleGenerator),
+						Example:      pAtt.Example(sds.Root.API.ExampleGenerator),
 					},
 				},
 			}
@@ -1150,7 +1164,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 					TypeRef:  sd.Scope.GoTypeRef(e.Body),
 					Type:     body,
 					Required: true,
-					Example:  e.Body.Example(expr.Root.API.ExampleGenerator),
+					Example:  e.Body.Example(sds.Root.API.ExampleGenerator),
 					Validate: svcode,
 				},
 			}}
@@ -1163,7 +1177,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 					TypeRef:  sd.Scope.GoTypeRefWithDefaults(e.Body),
 					Type:     body,
 					Required: true,
-					Example:  e.Body.Example(expr.Root.API.ExampleGenerator),
+					Example:  e.Body.Example(sds.Root.API.ExampleGenerator),
 					Validate: cvcode,
 				},
 			}}
@@ -1279,7 +1293,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 							Type:         uatt.Type,
 							Pointer:      sc.UsernamePointer,
 							Validate:     codegen.ValidationCode(uatt, nil, httpsvrctx, sc.UsernameRequired, expr.IsAlias(uatt.Type), false, sc.UsernameAttr),
-							Example:      uatt.Example(expr.Root.API.ExampleGenerator),
+							Example:      uatt.Example(sds.Root.API.ExampleGenerator),
 						},
 					}
 					patt := e.MethodExpr.Payload.Find(sc.PasswordAttr)
@@ -1302,7 +1316,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 							Type:         patt.Type,
 							Pointer:      sc.PasswordPointer,
 							Validate:     codegen.ValidationCode(patt, nil, httpsvrctx, sc.PasswordRequired, expr.IsAlias(patt.Type), false, sc.PasswordAttr),
-							Example:      patt.Example(expr.Root.API.ExampleGenerator),
+							Example:      patt.Example(sds.Root.API.ExampleGenerator),
 						},
 					}
 					cliArgs = []*InitArgData{uarg, parg}
@@ -1412,7 +1426,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 }
 
 // buildResultData builds the result data for the given service endpoint.
-func buildResultData(e *expr.HTTPEndpointExpr, sd *ServiceData) *ResultData {
+func (sds *ServicesData) buildResultData(e *expr.HTTPEndpointExpr, sd *ServiceData) *ResultData {
 	var (
 		svc    = sd.Service
 		ep     = svc.Method(e.MethodExpr.Name)
@@ -1442,7 +1456,7 @@ func buildResultData(e *expr.HTTPEndpointExpr, sd *ServiceData) *ResultData {
 			result = expr.AsObject(ep.ViewedResult.Type).Attribute("projected")
 			viewed = true
 		}
-		responses = buildResponses(e, result, viewed, sd)
+		responses = sds.buildResponses(e, result, viewed, sd)
 		for _, r := range responses {
 			// response has a body, headers, cookies or tag
 			if len(r.ServerBody) > 0 || len(r.Headers) > 0 || len(r.Cookies) > 0 || r.TagName != "" {
@@ -1465,7 +1479,7 @@ func buildResultData(e *expr.HTTPEndpointExpr, sd *ServiceData) *ResultData {
 // inferred from the method's result expression if not specified explicitly.
 //
 // viewed parameter indicates if the method result uses views.
-func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed bool, sd *ServiceData) []*ResponseData {
+func (sds *ServicesData) buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed bool, sd *ServiceData) []*ResponseData {
 	var (
 		responses []*ResponseData
 
@@ -1503,8 +1517,8 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed
 				resAttr = result
 			)
 			{
-				headersData = extractHeaders(resp.Headers, result, svcctx, scope)
-				cookiesData = extractCookies(resp.Cookies, result, svcctx, scope)
+				headersData = sds.extractHeaders(resp.Headers, result, svcctx, scope)
+				cookiesData = sds.extractCookies(resp.Cookies, result, svcctx, scope)
 				if resp.Body.Type != expr.Empty {
 					// If design uses Body("name") syntax we need to use the
 					// corresponding attribute in the result type for body
@@ -1519,14 +1533,14 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed
 					if origin != "" {
 						// Response body is explicitly set to an attribute in the method
 						// result type. No need to do any view-based projections server side.
-						if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &vname, sd); sbd != nil {
+						if sbd := sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &vname, sd); sbd != nil {
 							serverBodyData = append(serverBodyData, sbd)
 						}
 					} else if v, ok := e.MethodExpr.Result.Meta.Last(expr.ViewMetaKey); ok {
 						// Design explicitly sets the view to render the result.
 						// We generate only one server body type which will be rendered
 						// using the specified view.
-						if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &v, sd); sbd != nil {
+						if sbd := sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &v, sd); sbd != nil {
 							serverBodyData = append(serverBodyData, sbd)
 						}
 					} else {
@@ -1539,17 +1553,17 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed
 						// attributes defined in the view in the response (NOTE: a required
 						// attribute in the result type may not be present in all its views)
 						for _, view := range md.ViewedResult.Views {
-							if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &view.Name, sd); sbd != nil {
+							if sbd := sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &view.Name, sd); sbd != nil {
 								serverBodyData = append(serverBodyData, sbd)
 							}
 						}
 					}
-					clientBodyData = buildResponseBodyType(resp.Body, result, md.ResultLoc, e, false, &vname, sd)
+					clientBodyData = sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, false, &vname, sd)
 				} else {
-					if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, nil, sd); sbd != nil {
+					if sbd := sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, nil, sd); sbd != nil {
 						serverBodyData = append(serverBodyData, sbd)
 					}
-					clientBodyData = buildResponseBodyType(resp.Body, result, md.ResultLoc, e, false, nil, sd)
+					clientBodyData = sds.buildResponseBodyType(resp.Body, result, md.ResultLoc, e, false, nil, sd)
 				}
 				if clientBodyData != nil {
 					sd.ClientTypeNames[clientBodyData.Name] = false
@@ -1739,7 +1753,7 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed
 // buildErrorsData builds the error data for all the error responses in the
 // endpoint expression. The response headers, cookies and body for each response
 // are inferred from the method's error expression if not specified explicitly.
-func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupData {
+func (sds *ServicesData) buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupData {
 	var (
 		svc        = sd.Service
 		ep         = svc.Method(e.MethodExpr.Name)
@@ -1778,7 +1792,7 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat
 					AttributeData: &AttributeData{Name: "body", VarName: "body", TypeRef: sd.Scope.GoTypeRef(v.Response.Body)},
 				}}
 			}
-			for _, h := range extractHeaders(v.Response.Headers, v.AttributeExpr, errctx, sd.Scope) {
+			for _, h := range sds.extractHeaders(v.Response.Headers, v.AttributeExpr, errctx, sd.Scope) {
 				args = append(args, &InitArgData{
 					Ref: h.VarName,
 					AttributeData: &AttributeData{
@@ -1794,7 +1808,7 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat
 					},
 				})
 			}
-			for _, c := range extractCookies(v.Response.Cookies, v.AttributeExpr, errctx, sd.Scope) {
+			for _, c := range sds.extractCookies(v.Response.Cookies, v.AttributeExpr, errctx, sd.Scope) {
 				args = append(args, &InitArgData{
 					Ref: c.VarName,
 					AttributeData: &AttributeData{
@@ -1866,10 +1880,10 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat
 			)
 			{
 				errorLoc := ep.ErrorLocs[v.ErrorExpr.Name]
-				if sbd := buildResponseBodyType(v.Response.Body, v.AttributeExpr, errorLoc, e, true, nil, sd); sbd != nil {
+				if sbd := sds.buildResponseBodyType(v.Response.Body, v.AttributeExpr, errorLoc, e, true, nil, sd); sbd != nil {
 					serverBodyData = append(serverBodyData, sbd)
 				}
-				clientBodyData = buildResponseBodyType(v.Response.Body, v.AttributeExpr, errorLoc, e, false, nil, sd)
+				clientBodyData = sds.buildResponseBodyType(v.Response.Body, v.AttributeExpr, errorLoc, e, false, nil, sd)
 				if clientBodyData != nil {
 					sd.ClientTypeNames[clientBodyData.Name] = false
 					clientBodyData.Description = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body for the %q error.",
@@ -1879,8 +1893,8 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat
 				}
 			}
 
-			headers := extractHeaders(v.Response.Headers, v.AttributeExpr, errctx, sd.Scope)
-			cookies := extractCookies(v.Response.Cookies, v.AttributeExpr, errctx, sd.Scope)
+			headers := sds.extractHeaders(v.Response.Headers, v.AttributeExpr, errctx, sd.Scope)
+			cookies := sds.extractCookies(v.Response.Cookies, v.AttributeExpr, errctx, sd.Scope)
 			var mustValidate bool
 			for _, h := range headers {
 				if h.Validate != "" || h.Required || needConversion(h.Type) {
@@ -1962,7 +1976,7 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat
 // svr is true if the function is generated for server side code.
 //
 // sd is the service data
-func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExpr, svr bool, sd *ServiceData) *TypeData {
+func (sds *ServicesData) buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExpr, svr bool, sd *ServiceData) *TypeData {
 	if body.Type == expr.Empty {
 		return nil
 	}
@@ -2053,7 +2067,7 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 				TypeRef:  svc.Scope.GoFullTypeRef(att, pkg),
 				Type:     att.Type,
 				Validate: validateDef,
-				Example:  att.Example(expr.Root.API.ExampleGenerator),
+				Example:  att.Example(sds.Root.API.ExampleGenerator),
 			},
 		}
 		init = &InitData{
@@ -2074,7 +2088,7 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 		Init:        init,
 		ValidateDef: validateDef,
 		ValidateRef: validateRef,
-		Example:     body.Example(expr.Root.API.ExampleGenerator),
+		Example:     body.Example(sds.Root.API.ExampleGenerator),
 	}
 }
 
@@ -2089,7 +2103,7 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 // svr is true if the function is generated for server side code
 //
 // view is the view name to add as a suffix to the type name.
-func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location, e *expr.HTTPEndpointExpr, svr bool, view *string, sd *ServiceData) *TypeData {
+func (sds *ServicesData) buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location, e *expr.HTTPEndpointExpr, svr bool, view *string, sd *ServiceData) *TypeData {
 	if body.Type == expr.Empty {
 		return nil
 	}
@@ -2174,7 +2188,7 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location,
 		// For client side, we don't have to generate a separate body type per
 		// view. Hence the client types are collected in "analyze" function.
 		collectUserTypes(body.Type, func(ut expr.UserType) {
-			if d := attributeTypeData(ut, false, false, true, sd); d != nil {
+			if d := sds.attributeTypeData(ut, false, false, true, sd); d != nil {
 				sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d)
 			}
 		})
@@ -2241,7 +2255,7 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location,
 				TypeRef:  tref,
 				Type:     att.Type,
 				Validate: validateDef,
-				Example:  att.Example(expr.Root.API.ExampleGenerator),
+				Example:  att.Example(sds.Root.API.ExampleGenerator),
 			},
 		}
 		init = &InitData{
@@ -2262,12 +2276,12 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location,
 		Init:        init,
 		ValidateDef: validateDef,
 		ValidateRef: validateRef,
-		Example:     body.Example(expr.Root.API.ExampleGenerator),
+		Example:     body.Example(sds.Root.API.ExampleGenerator),
 		View:        viewName,
 	}
 }
 
-func extractPathParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope) []*ParamData {
+func (sds *ServicesData) extractPathParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope) []*ParamData {
 	var params []*ParamData
 	codegen.WalkMappedAttr(a, func(name, elem string, _ bool, c *expr.AttributeExpr) error { // nolint: errcheck
 		// The StringSlice field of ParamData must be false for aliased primitive types
@@ -2314,7 +2328,7 @@ func extractPathParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr,
 					Pointer:      false,
 					Validate:     codegen.AttributeValidationCode(c, nil, ctx, true, expr.IsAlias(c.Type), varn, name),
 					DefaultValue: c.DefaultValue,
-					Example:      c.Example(expr.Root.API.ExampleGenerator),
+					Example:      c.Example(sds.Root.API.ExampleGenerator),
 				},
 			},
 		})
@@ -2324,7 +2338,7 @@ func extractPathParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr,
 	return params
 }
 
-func extractQueryParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope) []*ParamData {
+func (sds *ServicesData) extractQueryParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope) []*ParamData {
 	var params []*ParamData
 	codegen.WalkMappedAttr(a, func(name, elem string, required bool, c *expr.AttributeExpr) error { // nolint: errcheck
 		// The StringSlice field of ParamData must be false for aliased primitive types
@@ -2381,7 +2395,7 @@ func extractQueryParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr
 					Pointer:      pointer,
 					Validate:     codegen.AttributeValidationCode(c, nil, ctx, required, expr.IsAlias(c.Type), varn, name),
 					DefaultValue: c.DefaultValue,
-					Example:      c.Example(expr.Root.API.ExampleGenerator),
+					Example:      c.Example(sds.Root.API.ExampleGenerator),
 				},
 			},
 		})
@@ -2391,7 +2405,7 @@ func extractQueryParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr
 	return params
 }
 
-func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svcCtx *codegen.AttributeContext, scope *codegen.NameScope) []*HeaderData {
+func (sds *ServicesData) extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svcCtx *codegen.AttributeContext, scope *codegen.NameScope) []*HeaderData {
 	var headers []*HeaderData
 	codegen.WalkMappedAttr(a, func(name, elem string, required bool, _ *expr.AttributeExpr) error { // nolint: errcheck
 		var attr *expr.AttributeExpr
@@ -2445,7 +2459,7 @@ func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
 					Type:         hattr.Type,
 					Validate:     codegen.AttributeValidationCode(hattr, nil, svcCtx, required, expr.IsAlias(hattr.Type), varn, name),
 					DefaultValue: hattr.DefaultValue,
-					Example:      hattr.Example(expr.Root.API.ExampleGenerator),
+					Example:      hattr.Example(sds.Root.API.ExampleGenerator),
 				},
 			},
 		})
@@ -2454,7 +2468,7 @@ func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
 	return headers
 }
 
-func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svcCtx *codegen.AttributeContext, scope *codegen.NameScope) []*CookieData {
+func (sds *ServicesData) extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svcCtx *codegen.AttributeContext, scope *codegen.NameScope) []*CookieData {
 	var cookies []*CookieData
 	codegen.WalkMappedAttr(a, func(name, elem string, required bool, _ *expr.AttributeExpr) error { // nolint: errcheck
 		var hattr *expr.AttributeExpr
@@ -2498,7 +2512,7 @@ func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
 					Type:         hattr.Type,
 					Validate:     codegen.AttributeValidationCode(hattr, nil, svcCtx, required, expr.IsAlias(hattr.Type), varn, name),
 					DefaultValue: hattr.DefaultValue,
-					Example:      hattr.Example(expr.Root.API.ExampleGenerator),
+					Example:      hattr.Example(sds.Root.API.ExampleGenerator),
 				},
 			},
 		}
@@ -2565,7 +2579,7 @@ func collectUserTypes(dt expr.DataType, cb func(expr.UserType), seen ...map[stri
 	}
 }
 
-func attributeTypeData(ut expr.UserType, req, ptr, server bool, rd *ServiceData) *TypeData {
+func (sds *ServicesData) attributeTypeData(ut expr.UserType, req, ptr, server bool, rd *ServiceData) *TypeData {
 	if ut == expr.Empty {
 		return nil
 	}
@@ -2610,7 +2624,7 @@ func attributeTypeData(ut expr.UserType, req, ptr, server bool, rd *ServiceData)
 		Ref:         rd.Scope.GoTypeRef(att),
 		ValidateDef: validate,
 		ValidateRef: validateRef,
-		Example:     att.Example(expr.Root.API.ExampleGenerator),
+		Example:     att.Example(sds.Root.API.ExampleGenerator),
 	}
 }
 
diff --git a/http/codegen/sse.go b/http/codegen/sse.go
index 9494f1a9..095ed744 100644
--- a/http/codegen/sse.go
+++ b/http/codegen/sse.go
@@ -105,8 +105,8 @@ func initSSEData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceData) {
 
 // sseServerFile returns the file implementing the SSE server
 // streaming implementation if any.
-func sseServerFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func sseServerFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	if data == nil {
 		return nil
 	}
diff --git a/http/codegen/sse_client.go b/http/codegen/sse_client.go
index 5852e932..1dabb163 100644
--- a/http/codegen/sse_client.go
+++ b/http/codegen/sse_client.go
@@ -10,8 +10,8 @@ import (
 
 // sseClientFile returns the file implementing the SSE client code for SSE endpoints if any.
 // Relies on SSEData (ed.SSE) for all codegen needs.
-func sseClientFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func sseClientFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	if data == nil {
 		return nil
 	}
diff --git a/http/codegen/templates/request_init.go.tpl b/http/codegen/templates/request_init.go.tpl
index c6306197..a3031e07 100644
--- a/http/codegen/templates/request_init.go.tpl
+++ b/http/codegen/templates/request_init.go.tpl
@@ -1,4 +1,3 @@
-
 {{- if or .Args .RequestStruct }}
 	var (
 	{{- range .Args }}
@@ -28,8 +27,8 @@
 		{{- if .Pointer }}
 		if p{{ if $.HasFields }}.{{ .FieldName }}{{ end }} != nil {
 		{{- end }}
-			{{- if (isAliased .FieldType) }}
-			{{ .VarName }} = {{ goTypeRef .Type $.ServiceName }}({{ if .Pointer }}*{{ end }}p{{ if $.HasFields }}.{{ .FieldName }}{{ end }})
+			{{- if .IsAliased }}
+			{{ .VarName }} = {{ .ServiceTypeRef }}({{ if .Pointer }}*{{ end }}p{{ if $.HasFields }}.{{ .FieldName }}{{ end }})
 			{{- else }}
 			{{ .VarName }} = {{ if .Pointer }}*{{ end }}p{{ if $.HasFields }}.{{ .FieldName }}{{ end }}
 			{{- end }}
diff --git a/http/codegen/websocket.go b/http/codegen/websocket.go
index d4304cc3..6a78e9db 100644
--- a/http/codegen/websocket.go
+++ b/http/codegen/websocket.go
@@ -74,7 +74,7 @@ type (
 )
 
 // initWebSocketData initializes the WebSocket related data in ed.
-func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceData) {
+func (sds *ServicesData) initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceData) {
 	if e.SSE != nil {
 		return
 	}
@@ -100,7 +100,7 @@ func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceDa
 	if e.MethodExpr.Stream == expr.ClientStreamKind || e.MethodExpr.Stream == expr.BidirectionalStreamKind {
 		svrRecvTypeName = sd.Scope.GoFullTypeName(e.MethodExpr.StreamingPayload, svc.PkgName)
 		svrRecvTypeRef = sd.Scope.GoFullTypeRef(e.MethodExpr.StreamingPayload, svc.PkgName)
-		svrPayload = buildRequestBodyType(e.StreamingBody, e.MethodExpr.StreamingPayload, e, true, sd)
+		svrPayload = sds.buildRequestBodyType(e.StreamingBody, e.MethodExpr.StreamingPayload, e, true, sd)
 		if needInit(e.MethodExpr.StreamingPayload.Type) {
 			makeHTTPType(e.StreamingBody)
 			body := e.StreamingBody.Type
@@ -144,7 +144,7 @@ func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceDa
 						TypeRef:  sd.Scope.GoTypeRef(e.StreamingBody),
 						Type:     e.StreamingBody.Type,
 						Required: true,
-						Example:  e.Body.Example(expr.Root.API.ExampleGenerator),
+						Example:  e.Body.Example(sds.Root.API.ExampleGenerator),
 						Validate: svcode,
 					},
 				}}
@@ -171,7 +171,7 @@ func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceDa
 				ServerCode:     serverCode,
 			}
 		}
-		cliPayload = buildRequestBodyType(e.StreamingBody, e.MethodExpr.StreamingPayload, e, false, sd)
+		cliPayload = sds.buildRequestBodyType(e.StreamingBody, e.MethodExpr.StreamingPayload, e, false, sd)
 		if cliPayload != nil {
 			sd.ClientTypeNames[cliPayload.Name] = false
 			sd.ServerTypeNames[cliPayload.Name] = false
@@ -238,8 +238,8 @@ func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceDa
 
 // websocketServerFile returns the file implementing the WebSocket server
 // streaming implementation if any.
-func websocketServerFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func websocketServerFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	if !hasWebSocket(data) {
 		return nil
 	}
@@ -271,8 +271,8 @@ func websocketServerFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File
 
 // websocketClientFile returns the file implementing the WebSocket client
 // streaming implementation if any.
-func websocketClientFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
-	data := HTTPServices.Get(svc.Name())
+func websocketClientFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData) *codegen.File {
+	data := services.Get(svc.Name())
 	if !hasWebSocket(data) {
 		return nil
 	}


Test patch:
diff --git a/codegen/example/example_client_test.go b/codegen/example/example_client_test.go
index e48d5d59..f89cc823 100644
--- a/codegen/example/example_client_test.go
+++ b/codegen/example/example_client_test.go
@@ -9,7 +9,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/example/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestExampleCLIFiles(t *testing.T) {
@@ -27,8 +26,8 @@ func TestExampleCLIFiles(t *testing.T) {
 		t.Run(c.Name, func(t *testing.T) {
 			// reset global variable
 			Servers = make(ServersData)
-			codegen.RunDSL(t, c.DSL)
-			fs := CLIFiles("", expr.Root)
+			root := codegen.RunDSL(t, c.DSL)
+			fs := CLIFiles("", root)
 			require.Len(t, fs, 1)
 			require.Greater(t, len(fs[0].SectionTemplates), 0)
 			var buf bytes.Buffer
diff --git a/codegen/example/example_server_test.go b/codegen/example/example_server_test.go
index a318b743..59f22426 100644
--- a/codegen/example/example_server_test.go
+++ b/codegen/example/example_server_test.go
@@ -14,7 +14,6 @@ import (
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/example/testdata"
 	"goa.design/goa/v3/codegen/service"
-	"goa.design/goa/v3/expr"
 )
 
 // updateGolden is true when -w is passed to `go test`, e.g. `go test ./... -w`
@@ -60,10 +59,10 @@ func TestExampleServerFiles(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			service.Services = make(service.ServicesData)
 			Servers = make(ServersData)
-			codegen.RunDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := codegen.RunDSL(t, c.DSL)
+			services := service.NewServicesData(root)
+			fs := ServerFiles("", root, services)
 			require.Len(t, fs, 1)
 			require.Greater(t, len(fs[0].SectionTemplates), 0)
 			var buf bytes.Buffer
diff --git a/codegen/service/client_test.go b/codegen/service/client_test.go
index 6cfda7d4..037d75d8 100644
--- a/codegen/service/client_test.go
+++ b/codegen/service/client_test.go
@@ -11,7 +11,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestClient(t *testing.T) {
@@ -36,9 +35,10 @@ func TestClient(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := ClientFile("test/gen", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := ClientFile("test/gen", root.Services[0], services)
 			require.NotNil(t, fs)
 			buf := new(bytes.Buffer)
 			for _, s := range fs.SectionTemplates[1:] {
diff --git a/codegen/service/convert_test.go b/codegen/service/convert_test.go
index 07704047..c723f40c 100644
--- a/codegen/service/convert_test.go
+++ b/codegen/service/convert_test.go
@@ -234,8 +234,9 @@ func TestConvertFile(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
 			root := runDSL(t, c.DSL)
+			services := NewServicesData(root)
 			for _, svc := range root.Services {
-				f, err := ConvertFile(root, svc)
+				f, err := ConvertFile(root, svc, services)
 				require.NoError(t, err)
 				require.NotNil(t, f)
 				sections := f.SectionTemplates
diff --git a/codegen/service/endpoint_test.go b/codegen/service/endpoint_test.go
index 2132eeee..02b8f638 100644
--- a/codegen/service/endpoint_test.go
+++ b/codegen/service/endpoint_test.go
@@ -11,7 +11,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestEndpoint(t *testing.T) {
@@ -39,9 +38,10 @@ func TestEndpoint(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := EndpointFile("goa.design/goa/example", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := EndpointFile("goa.design/goa/example", root.Services[0], services)
 			require.NotNil(t, fs)
 			buf := new(bytes.Buffer)
 			for _, s := range fs.SectionTemplates[1:] {
diff --git a/codegen/service/example_interceptors_test.go b/codegen/service/example_interceptors_test.go
index d4e6faf1..4235a2f3 100644
--- a/codegen/service/example_interceptors_test.go
+++ b/codegen/service/example_interceptors_test.go
@@ -85,10 +85,11 @@ func TestExampleInterceptorsFiles(t *testing.T) {
 		t.Run(c.Name, func(t *testing.T) {
 			// Run DSL
 			root := expr.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
 			require.NotNil(t, root)
 
 			// Generate files
-			fs := ExampleInterceptorsFiles("", root)
+			fs := ExampleInterceptorsFiles("", root, services)
 			require.Len(t, fs, len(c.ExpectedFiles))
 
 			// Verify file paths
diff --git a/codegen/service/example_svc_test.go b/codegen/service/example_svc_test.go
index a45b600a..0852d339 100644
--- a/codegen/service/example_svc_test.go
+++ b/codegen/service/example_svc_test.go
@@ -9,7 +9,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestExampleServiceFiles(t *testing.T) {
@@ -32,9 +31,10 @@ func TestExampleServiceFiles(t *testing.T) {
 		}
 		for _, c := range cases {
 			t.Run(c.Name, func(t *testing.T) {
-				codegen.RunDSL(t, c.DSL)
-				require.Len(t, expr.Root.Services, 3)
-				fs := ExampleServiceFiles("", expr.Root)
+				root := codegen.RunDSL(t, c.DSL)
+				services := NewServicesData(root)
+				require.Len(t, root.Services, 3)
+				fs := ExampleServiceFiles("", root, services)
 				require.Len(t, fs, 3)
 				for _, f := range fs {
 					require.Greater(t, len(f.SectionTemplates), 0)
diff --git a/codegen/service/interceptors_test.go b/codegen/service/interceptors_test.go
index 7bb993d5..0dff389b 100644
--- a/codegen/service/interceptors_test.go
+++ b/codegen/service/interceptors_test.go
@@ -51,9 +51,10 @@ func TestInterceptors(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
 			root := runDSL(t, c.DSL)
+			services := NewServicesData(root)
 			require.Len(t, root.Services, 1)
 
-			fs := InterceptorsFiles("goa.design/goa/example", root.Services[0])
+			fs := InterceptorsFiles("goa.design/goa/example", root.Services[0], services)
 
 			require.Len(t, fs, c.expectedFileCount)
 			for _, f := range fs {
diff --git a/codegen/service/security_test.go b/codegen/service/security_test.go
index 47074ed7..0592a5f6 100644
--- a/codegen/service/security_test.go
+++ b/codegen/service/security_test.go
@@ -8,7 +8,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestSecureEndpointInit(t *testing.T) {
@@ -24,9 +23,10 @@ func TestSecureEndpointInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := EndpointFile("", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := EndpointFile("", root.Services[0], services)
 			require.NotNil(t, fs)
 			sections := fs.SectionTemplates
 			require.Greater(t, len(sections), 1)
@@ -49,9 +49,10 @@ func TestSecureEndpoint(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := EndpointFile("", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := EndpointFile("", root.Services[0], services)
 			require.NotNil(t, fs)
 			sections := fs.SectionTemplates
 			code := codegen.SectionCode(t, sections[4])
@@ -70,9 +71,10 @@ func TestSecureWithSkipRequestBodyEncodeDecode(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := EndpointFile("", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := EndpointFile("", root.Services[0], services)
 			require.NotNil(t, fs)
 			sections := fs.SectionTemplates
 			code := codegen.SectionCode(t, sections[5])
diff --git a/codegen/service/service_test.go b/codegen/service/service_test.go
index 6d0197e6..f9e6e07b 100644
--- a/codegen/service/service_test.go
+++ b/codegen/service/service_test.go
@@ -12,7 +12,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestService(t *testing.T) {
@@ -61,9 +60,10 @@ func TestService(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			files := Files("goa.design/goa/example", expr.Root.Services[0], make(map[string][]string))
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			files := Files("goa.design/goa/example", root.Services[0], services, make(map[string][]string))
 			require.Greater(t, len(files), 0)
 			validateFile(t, files[0], files[0].Path, c.Code)
 		})
@@ -94,11 +94,12 @@ func TestStructPkgPath(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
 			userTypePkgs := make(map[string][]string)
-			codegen.RunDSL(t, c.DSL)
-			if len(expr.Root.Services) != len(c.SvcCodes) {
-				t.Fatalf("got %d services, expected %d", len(expr.Root.Services), len(c.SvcCodes))
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			if len(root.Services) != len(c.SvcCodes) {
+				t.Fatalf("got %d services, expected %d", len(root.Services), len(c.SvcCodes))
 			}
-			files := Files("goa.design/goa/example", expr.Root.Services[0], userTypePkgs)
+			files := Files("goa.design/goa/example", root.Services[0], services, userTypePkgs)
 			if len(files) != len(c.TypeFiles)+1 {
 				t.Fatalf("got %d files, expected %d", len(files), len(c.TypeFiles)+1)
 			}
@@ -107,7 +108,7 @@ func TestStructPkgPath(t *testing.T) {
 				validateFile(t, files[i+1], f, c.TypeCodes[i])
 			}
 			if len(c.SvcCodes) > 1 {
-				files = Files("goa.design/goa/example", expr.Root.Services[1], userTypePkgs)
+				files = Files("goa.design/goa/example", root.Services[1], services, userTypePkgs)
 				require.Len(t, files, 1)
 				validateFile(t, files[0], files[0].Path, c.SvcCodes[1])
 			}
diff --git a/codegen/service/testing.go b/codegen/service/testing.go
index 28243ecf..aae77fe6 100644
--- a/codegen/service/testing.go
+++ b/codegen/service/testing.go
@@ -12,7 +12,6 @@ import (
 // initDSL initializes the DSL environment and returns the root.
 func initDSL(t *testing.T) *expr.RootExpr {
 	// reset all roots and codegen data structures
-	Services = make(ServicesData)
 	eval.Reset()
 	expr.Root = new(expr.RootExpr)
 	expr.GeneratedResultTypes = new(expr.ResultTypesRoot)
diff --git a/codegen/service/views_test.go b/codegen/service/views_test.go
index ed769e85..a5abe687 100644
--- a/codegen/service/views_test.go
+++ b/codegen/service/views_test.go
@@ -11,7 +11,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/service/testdata"
-	"goa.design/goa/v3/expr"
 )
 
 func TestViews(t *testing.T) {
@@ -33,9 +32,10 @@ func TestViews(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			codegen.RunDSL(t, c.DSL)
-			require.Len(t, expr.Root.Services, 1)
-			fs := ViewsFile("goa.design/goa/example", expr.Root.Services[0])
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(root)
+			require.Len(t, root.Services, 1)
+			fs := ViewsFile("goa.design/goa/example", root.Services[0], services)
 			require.NotNil(t, fs)
 			buf := new(bytes.Buffer)
 			for _, s := range fs.SectionTemplates[1:] {
diff --git a/grpc/codegen/client_cli_test.go b/grpc/codegen/client_cli_test.go
index 35bae4f4..c7e6483f 100644
--- a/grpc/codegen/client_cli_test.go
+++ b/grpc/codegen/client_cli_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -24,8 +23,9 @@ func TestClientCLIFiles(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientCLIFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientCLIFiles("", services)
 			require.Greater(t, len(fs), 1, "expected at least 2 files")
 			require.NotEmpty(t, fs[1].SectionTemplates)
 			var buf bytes.Buffer
diff --git a/grpc/codegen/client_test.go b/grpc/codegen/client_test.go
index 93edbf23..d1c6ba1f 100644
--- a/grpc/codegen/client_test.go
+++ b/grpc/codegen/client_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -32,8 +31,9 @@ func TestClientEndpointInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[0].Section("client-endpoint-init")
 			if len(sections) == 0 {
@@ -63,8 +63,9 @@ func TestRequestEncoder(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].Section("request-encoder")
 			require.NotEmpty(t, sections)
@@ -94,8 +95,9 @@ func TestResponseDecoder(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].Section("response-decoder")
 			require.NotEmpty(t, sections)
diff --git a/grpc/codegen/client_types_test.go b/grpc/codegen/client_types_test.go
index 549ef062..9f944b31 100644
--- a/grpc/codegen/client_types_test.go
+++ b/grpc/codegen/client_types_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -31,8 +30,9 @@ func TestClientTypeFiles(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientTypeFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientTypeFiles("", services)
 			require.Len(t, fs, 1)
 			var buf bytes.Buffer
 			for _, s := range fs[0].SectionTemplates[1:] {
diff --git a/grpc/codegen/example_cli_test.go b/grpc/codegen/example_cli_test.go
index aeb5fffe..10ce1337 100644
--- a/grpc/codegen/example_cli_test.go
+++ b/grpc/codegen/example_cli_test.go
@@ -9,7 +9,7 @@ import (
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/example"
 	ctestdata "goa.design/goa/v3/codegen/example/testdata"
-	"goa.design/goa/v3/expr"
+	"goa.design/goa/v3/codegen/service"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -31,8 +31,9 @@ func TestExampleCLIFiles(t *testing.T) {
 		t.Run(c.Name, func(t *testing.T) {
 			// reset global variable
 			example.Servers = make(example.ServersData)
-			codegen.RunDSL(t, c.DSL)
-			fs := ExampleCLIFiles(c.PkgPath, expr.Root)
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(service.NewServicesData(root))
+			fs := ExampleCLIFiles(c.PkgPath, services)
 			require.Greater(t, len(fs), 0)
 			require.Greater(t, len(fs[0].SectionTemplates), 0)
 			var buf bytes.Buffer
diff --git a/grpc/codegen/example_server_test.go b/grpc/codegen/example_server_test.go
index 57e6c6d5..1b31a18d 100644
--- a/grpc/codegen/example_server_test.go
+++ b/grpc/codegen/example_server_test.go
@@ -14,7 +14,6 @@ import (
 	"goa.design/goa/v3/codegen/example"
 	ctestdata "goa.design/goa/v3/codegen/example/testdata"
 	"goa.design/goa/v3/codegen/service"
-	"goa.design/goa/v3/expr"
 )
 
 var updateGolden = false
@@ -50,11 +49,10 @@ func TestExampleServerFiles(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
 			// reset global variable
-			GRPCServices = make(ServicesData)
-			service.Services = make(service.ServicesData)
 			example.Servers = make(example.ServersData)
-			codegen.RunDSL(t, c.DSL)
-			fs := ExampleServerFiles("", expr.Root)
+			root := codegen.RunDSL(t, c.DSL)
+			services := NewServicesData(service.NewServicesData(root))
+			fs := ExampleServerFiles("", services)
 			require.Greater(t, len(fs), 0)
 			require.Greater(t, len(fs[0].SectionTemplates), 0)
 			var buf bytes.Buffer
diff --git a/grpc/codegen/parse_endpoint_test.go b/grpc/codegen/parse_endpoint_test.go
index 3a459e4b..0e767491 100644
--- a/grpc/codegen/parse_endpoint_test.go
+++ b/grpc/codegen/parse_endpoint_test.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/stretchr/testify/require"
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -24,8 +23,9 @@ func TestParseEndpointWithInterceptors(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ClientCLIFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ClientCLIFiles("", services)
 			require.Greater(t, len(fs), 1, "expected at least 2 files")
 			require.NotEmpty(t, fs[0].SectionTemplates)
 			var buf bytes.Buffer
diff --git a/grpc/codegen/proto_test.go b/grpc/codegen/proto_test.go
index 1d05bb98..7039cc2a 100644
--- a/grpc/codegen/proto_test.go
+++ b/grpc/codegen/proto_test.go
@@ -12,7 +12,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -39,8 +38,9 @@ func TestProtoFiles(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ProtoFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ProtoFiles("", services)
 			if len(fs) != 1 {
 				t.Fatalf("got %d files, expected one", len(fs))
 			}
@@ -76,8 +76,9 @@ func TestMessageDefSection(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ProtoFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ProtoFiles("", services)
 			require.Len(t, fs, 1)
 			sections := fs[0].SectionTemplates
 			require.GreaterOrEqual(t, len(sections), 3)
diff --git a/grpc/codegen/server_test.go b/grpc/codegen/server_test.go
index 83dac456..87cc8867 100644
--- a/grpc/codegen/server_test.go
+++ b/grpc/codegen/server_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -31,8 +30,9 @@ func TestServerGRPCInterface(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[0].Section("server-grpc-interface")
 			require.NotEmpty(t, sections)
@@ -59,8 +59,9 @@ func TestServerHandlerInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[0].Section("grpc-handler-init")
 			require.NotEmpty(t, sections)
@@ -88,8 +89,9 @@ func TestRequestDecoder(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].Section("request-decoder")
 			require.NotEmpty(t, sections)
@@ -116,8 +118,9 @@ func TestResponseEncoder(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].Section("response-encoder")
 			require.NotEmpty(t, sections)
diff --git a/grpc/codegen/server_types_test.go b/grpc/codegen/server_types_test.go
index f557b1da..c2d6e21b 100644
--- a/grpc/codegen/server_types_test.go
+++ b/grpc/codegen/server_types_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -33,8 +32,9 @@ func TestServerTypeFiles(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			fs := ServerTypeFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			fs := ServerTypeFiles("", services)
 			require.Len(t, fs, 1)
 			var buf bytes.Buffer
 			for _, s := range fs[0].SectionTemplates[1:] {
diff --git a/grpc/codegen/streaming_test.go b/grpc/codegen/streaming_test.go
index 1e3c4b36..04ea2862 100644
--- a/grpc/codegen/streaming_test.go
+++ b/grpc/codegen/streaming_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/grpc/codegen/testdata"
 )
 
@@ -105,12 +104,13 @@ func TestStreaming(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunGRPCDSL(t, c.DSL)
-			serverfs := ServerFiles("", expr.Root)
+			root := RunGRPCDSL(t, c.DSL)
+			services := CreateGRPCServices(root)
+			serverfs := ServerFiles("", services)
 			if len(serverfs) < 2 {
 				t.Fatalf("got %d server files, expected 2", len(serverfs))
 			}
-			clientfs := ClientFiles("", expr.Root)
+			clientfs := ClientFiles("", services)
 			if len(clientfs) < 2 {
 				t.Fatalf("got %d client files, expected 2", len(clientfs))
 			}
diff --git a/grpc/codegen/testing.go b/grpc/codegen/testing.go
index 5044a485..9d0933eb 100644
--- a/grpc/codegen/testing.go
+++ b/grpc/codegen/testing.go
@@ -13,9 +13,13 @@ import (
 // It is used only in tests.
 func RunGRPCDSL(t *testing.T, dsl func()) *expr.RootExpr {
 	// reset all roots and codegen data structures
-	service.Services = make(service.ServicesData)
-	GRPCServices = make(ServicesData)
-	return expr.RunDSL(t, dsl)
+	root := expr.RunDSL(t, dsl)
+	return root
+}
+
+// CreateGRPCServices creates a new ServicesData instance for testing.
+func CreateGRPCServices(root *expr.RootExpr) *ServicesData {
+	return NewServicesData(service.NewServicesData(root))
 }
 
 func sectionCode(t *testing.T, section ...*codegen.SectionTemplate) string {
diff --git a/http/codegen/client_body_types_test.go b/http/codegen/client_body_types_test.go
index 4504348f..03bb4f00 100644
--- a/http/codegen/client_body_types_test.go
+++ b/http/codegen/client_body_types_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -25,8 +24,9 @@ func TestBodyTypeDecl(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := clientType(genpkg, expr.Root.API.HTTP.Services[0], make(map[string]struct{}))
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := clientType(genpkg, root.API.HTTP.Services[0], make(map[string]struct{}), services)
 			section := fs.SectionTemplates[1]
 			code := codegen.SectionCode(t, section)
 			assert.Equal(t, c.Code, code)
@@ -56,8 +56,9 @@ func TestBodyTypeInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := clientType(genpkg, expr.Root.API.HTTP.Services[0], make(map[string]struct{}))
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := clientType(genpkg, root.API.HTTP.Services[0], make(map[string]struct{}), services)
 			section := fs.SectionTemplates[c.SectionIndex]
 			code := codegen.SectionCode(t, section)
 			assert.Equal(t, c.Code, code)
@@ -88,8 +89,9 @@ func TestClientTypes(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := clientType(genpkg, expr.Root.API.HTTP.Services[0], make(map[string]struct{}))
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := clientType(genpkg, root.API.HTTP.Services[0], make(map[string]struct{}), services)
 			var buf bytes.Buffer
 			for _, s := range fs.SectionTemplates[1:] {
 				require.NoError(t, s.Write(&buf))
@@ -111,8 +113,9 @@ func TestClientTypeFiles(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fw := ClientTypeFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fw := ClientTypeFiles(genpkg, services)
 			for i, fs := range fw {
 				var buf bytes.Buffer
 				for _, s := range fs.SectionTemplates[1:] {
diff --git a/http/codegen/client_cli_test.go b/http/codegen/client_cli_test.go
index dbef28cc..60381052 100644
--- a/http/codegen/client_cli_test.go
+++ b/http/codegen/client_cli_test.go
@@ -6,7 +6,6 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -54,8 +53,9 @@ func TestClientCLIFiles(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientCLIFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientCLIFiles("", services)
 			sections := fs[c.FileIndex].SectionTemplates
 			code := codegen.SectionCode(t, sections[c.SectionIndex])
 			assert.Equal(t, c.Code, code)
diff --git a/http/codegen/client_decode_test.go b/http/codegen/client_decode_test.go
index 91368888..373c0767 100644
--- a/http/codegen/client_decode_test.go
+++ b/http/codegen/client_decode_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -37,8 +36,9 @@ func TestClientDecode(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 2)
diff --git a/http/codegen/client_encode_test.go b/http/codegen/client_encode_test.go
index b1f28eab..7210a9a2 100644
--- a/http/codegen/client_encode_test.go
+++ b/http/codegen/client_encode_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -189,8 +188,9 @@ func TestClientEncode(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles("", services)
 			assert.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			assert.Greater(t, len(sections), 2)
@@ -222,8 +222,9 @@ func TestClientBuildRequest(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 2)
diff --git a/http/codegen/client_init_test.go b/http/codegen/client_init_test.go
index 0f397afe..aecbaf03 100644
--- a/http/codegen/client_init_test.go
+++ b/http/codegen/client_init_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -24,8 +23,9 @@ func TestClientInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles("", services)
 			require.Len(t, fs, c.FileCount)
 			sections := fs[0].SectionTemplates
 			require.Greater(t, len(sections), c.SectionNum)
diff --git a/http/codegen/example_cli_test.go b/http/codegen/example_cli_test.go
index 6b12fea1..ef99cfdf 100644
--- a/http/codegen/example_cli_test.go
+++ b/http/codegen/example_cli_test.go
@@ -10,7 +10,7 @@ import (
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/example"
 	ctestdata "goa.design/goa/v3/codegen/example/testdata"
-	"goa.design/goa/v3/expr"
+	"goa.design/goa/v3/codegen/service"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -29,8 +29,9 @@ func TestExampleCLIFiles(t *testing.T) {
 		t.Run(c.Name, func(t *testing.T) {
 			// reset global variable
 			example.Servers = make(example.ServersData)
-			codegen.RunDSL(t, c.DSL)
-			fs := ExampleCLIFiles("", expr.Root)
+			root := codegen.RunDSL(t, c.DSL)
+			httpServices := NewServicesData(service.NewServicesData(root))
+			fs := ExampleCLIFiles("", httpServices)
 			require.Len(t, fs, 1)
 			require.Greater(t, len(fs[0].SectionTemplates), 0)
 			var buf bytes.Buffer
diff --git a/http/codegen/example_server_test.go b/http/codegen/example_server_test.go
index e0f61014..bb0e4f86 100644
--- a/http/codegen/example_server_test.go
+++ b/http/codegen/example_server_test.go
@@ -15,7 +15,6 @@ import (
 	"goa.design/goa/v3/codegen/example"
 	ctestdata "goa.design/goa/v3/codegen/example/testdata"
 	"goa.design/goa/v3/codegen/service"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -56,12 +55,11 @@ func TestExampleServerFiles(t *testing.T) {
 		for _, c := range cases {
 			t.Run(c.Name, func(t *testing.T) {
 				// reset global variable
-				HTTPServices = make(ServicesData)
-				service.Services = make(service.ServicesData)
 				example.Servers = make(example.ServersData)
-				codegen.RunDSL(t, c.DSL)
-				require.Len(t, expr.Root.Services, 3)
-				fs := ExampleServerFiles("", expr.Root)
+				root := codegen.RunDSL(t, c.DSL)
+				require.Len(t, root.Services, 3)
+				httpServices := NewServicesData(service.NewServicesData(root))
+				fs := ExampleServerFiles("", httpServices)
 				require.Len(t, fs, 2)
 				for i, f := range fs {
 					if i < len(fs)-1 {
@@ -94,11 +92,10 @@ func TestExampleServerFiles(t *testing.T) {
 		for _, c := range cases {
 			t.Run(c.Name, func(t *testing.T) {
 				// reset global variable
-				HTTPServices = make(ServicesData)
-				service.Services = make(service.ServicesData)
 				example.Servers = make(example.ServersData)
-				codegen.RunDSL(t, c.DSL)
-				fs := ExampleServerFiles("", expr.Root)
+				root := codegen.RunDSL(t, c.DSL)
+				httpServices := NewServicesData(service.NewServicesData(root))
+				fs := ExampleServerFiles("", httpServices)
 				require.Len(t, fs, 1)
 				require.Greater(t, len(fs[0].SectionTemplates), 0)
 				var buf bytes.Buffer
diff --git a/http/codegen/handler_test.go b/http/codegen/handler_test.go
index 94d7f71c..2bc56047 100644
--- a/http/codegen/handler_test.go
+++ b/http/codegen/handler_test.go
@@ -9,7 +9,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/codegentest"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -31,8 +30,9 @@ func TestHandlerInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			sections := codegentest.Sections(fs, filepath.Join("", "server.go"), "server-handler-init")
 			require.Greater(t, len(sections), 0)
 			code := codegen.SectionCode(t, sections[0])
diff --git a/http/codegen/multipart_test.go b/http/codegen/multipart_test.go
index 36ff4d1a..e29d64d2 100644
--- a/http/codegen/multipart_test.go
+++ b/http/codegen/multipart_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -25,8 +24,9 @@ func TestServerMultipartFuncType(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			require.Len(t, fs, 2)
 			sections := fs[0].SectionTemplates
 			require.Greater(t, len(sections), 5)
@@ -50,8 +50,9 @@ func TestClientMultipartFuncType(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles(genpkg, services)
 			require.Len(t, fs, 2)
 			sections := fs[0].SectionTemplates
 			require.Greater(t, len(sections), 4)
@@ -77,8 +78,9 @@ func TestServerMultipartNewFunc(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 3)
@@ -104,8 +106,9 @@ func TestClientMultipartNewFunc(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ClientFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ClientFiles(genpkg, services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 3)
diff --git a/http/codegen/openapi/v2/builder_test.go b/http/codegen/openapi/v2/builder_test.go
index 5bee107b..f469bb94 100644
--- a/http/codegen/openapi/v2/builder_test.go
+++ b/http/codegen/openapi/v2/builder_test.go
@@ -77,7 +77,8 @@ func TestBuildPathFromExpr(t *testing.T) {
 			},
 		},
 	}
-	expr.Root.API = &expr.APIExpr{
+	var root expr.RootExpr
+	root.API = &expr.APIExpr{
 		HTTP: &expr.HTTPExpr{
 			Path: "/",
 		},
@@ -89,8 +90,13 @@ func TestBuildPathFromExpr(t *testing.T) {
 				Paths:    make(map[string]any),
 			}
 			root := &expr.RootExpr{
-				API: &expr.APIExpr{},
+				API: &expr.APIExpr{
+					HTTP: &expr.HTTPExpr{
+						Path: "/",
+					},
+				},
 			}
+			expr.Root = root
 			h := &expr.HostExpr{}
 			route := &expr.RouteExpr{
 				Method: "POST",
diff --git a/http/codegen/openapi/v3/builder_test.go b/http/codegen/openapi/v3/builder_test.go
index 0755d4bd..81a3fa67 100644
--- a/http/codegen/openapi/v3/builder_test.go
+++ b/http/codegen/openapi/v3/builder_test.go
@@ -189,13 +189,13 @@ func TestBuildOperation(t *testing.T) {
 	}}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			api := codegen.RunDSL(t, c.DSL).API
+			root := codegen.RunDSL(t, c.DSL)
 
 			var bodies *EndpointBodies
 			var types map[string]*openapi.Schema
 			{
 				var bds map[string]map[string]*EndpointBodies
-				bds, types = buildBodyTypes(api)
+				bds, types = buildBodyTypes(root.API, root.Types, root.ResultTypes)
 				if svc, ok := bds[svcName]; ok {
 					bodies, ok = svc[c.Name]
 					if !ok {
@@ -206,30 +206,28 @@ func TestBuildOperation(t *testing.T) {
 			}
 
 			var route *expr.RouteExpr
-			{
-				if len(api.HTTP.Services) == 0 {
-					t.Error("no HTTP service created from DSL")
-				}
-				for _, s := range api.HTTP.Services {
-					if s.Name() == svcName {
-						for _, e := range s.HTTPEndpoints {
-							if e.Name() == c.Name {
-								route = e.Routes[0]
-								break
-							}
+			if len(root.API.HTTP.Services) == 0 {
+				t.Error("no HTTP service created from DSL")
+			}
+			for _, s := range root.API.HTTP.Services {
+				if s.Name() == svcName {
+					for _, e := range s.HTTPEndpoints {
+						if e.Name() == c.Name {
+							route = e.Routes[0]
+							break
 						}
 					}
-					if route != nil {
-						break
-					}
 				}
-				if route == nil {
-					t.Error("could not find route")
-					return
+				if route != nil {
+					break
 				}
 			}
+			if route == nil {
+				t.Error("could not find route")
+				return
+			}
 
-			op := buildOperation(c.Name, route, bodies, expr.NewRandom(c.Name))
+			op := buildOperation(c.Name, route, bodies, expr.NewRandom(c.Name), root.API.Meta)
 
 			if op.Description != c.ExpectedDescription {
 				t.Errorf("got description %q for method %q, expected %q", op.Description, c.Name, c.ExpectedDescription)
@@ -318,7 +316,7 @@ func TestBuildOperationID(t *testing.T) {
 				if s.Name() == svcName {
 					for _, e := range s.HTTPEndpoints {
 						for i, r := range e.Routes {
-							op := buildOperation(c.Name, r, &EndpointBodies{}, expr.NewRandom(c.Name))
+							op := buildOperation(c.Name, r, &EndpointBodies{}, expr.NewRandom(c.Name), api.Meta)
 
 							if len(c.ExpectedOperationIDs) == 0 {
 								t.Error("no expected operation IDs")
diff --git a/http/codegen/openapi/v3/types_test.go b/http/codegen/openapi/v3/types_test.go
index 574bf3a1..e329bc59 100644
--- a/http/codegen/openapi/v3/types_test.go
+++ b/http/codegen/openapi/v3/types_test.go
@@ -169,9 +169,9 @@ func TestBuildBodyTypes(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			api := codegen.RunDSL(t, c.DSL).API
+			root := codegen.RunDSL(t, c.DSL)
 
-			bodies, types := buildBodyTypes(api)
+			bodies, types := buildBodyTypes(root.API, root.Types, root.ResultTypes)
 
 			svc, ok := bodies[svcName]
 			if !ok {
@@ -257,9 +257,9 @@ func matchesSchemaWithPrefix(t *testing.T, ctx string, s *openapi.Schema, types
 }
 
 func TestTypesOnlyDifferByEnum(t *testing.T) {
-	api := codegen.RunDSL(t, dsls.StringEnumBodyDSL()).API
+	root := codegen.RunDSL(t, dsls.StringEnumBodyDSL())
 
-	bodies, types := buildBodyTypes(api)
+	bodies, types := buildBodyTypes(root.API, root.Types, root.ResultTypes)
 
 	svc1, ok := bodies["svc_enum_1"]
 	if !ok {
diff --git a/http/codegen/paths_test.go b/http/codegen/paths_test.go
index 0e2ddb08..0299b7f4 100644
--- a/http/codegen/paths_test.go
+++ b/http/codegen/paths_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -36,9 +35,10 @@ func TestPaths(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			require.Len(t, expr.Root.API.HTTP.Services, 1)
-			fs := serverPath(expr.Root.API.HTTP.Services[0])
+			root := RunHTTPDSL(t, c.DSL)
+			require.Len(t, root.API.HTTP.Services, 1)
+			services := CreateHTTPServices(root)
+			fs := serverPath(root.API.HTTP.Services[0], services)
 			sections := fs.SectionTemplates
 			code := codegen.SectionCode(t, sections[1])
 			assert.Equal(t, c.Code, code)
@@ -62,9 +62,10 @@ func TestPathTrailingShash(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			require.Len(t, expr.Root.API.HTTP.Services, 1)
-			fs := serverPath(expr.Root.API.HTTP.Services[0])
+			root := RunHTTPDSL(t, c.DSL)
+			require.Len(t, root.API.HTTP.Services, 1)
+			services := CreateHTTPServices(root)
+			fs := serverPath(root.API.HTTP.Services[0], services)
 			sections := fs.SectionTemplates
 			code := codegen.SectionCode(t, sections[1])
 			assert.Equal(t, c.Code, code)
diff --git a/http/codegen/server_decode_test.go b/http/codegen/server_decode_test.go
index dc3065ae..ebd3ed17 100644
--- a/http/codegen/server_decode_test.go
+++ b/http/codegen/server_decode_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -226,8 +225,9 @@ func TestDecode(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 2)
diff --git a/http/codegen/server_encode_test.go b/http/codegen/server_encode_test.go
index 80d8fb0b..7b244485 100644
--- a/http/codegen/server_encode_test.go
+++ b/http/codegen/server_encode_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -92,8 +91,9 @@ func TestEncode(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 1)
@@ -125,8 +125,9 @@ func TestEncodeMarshallingAndUnmarshalling(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			totalSectionsExpected := c.SectionsOffset + len(c.Code)
diff --git a/http/codegen/server_error_encoder_test.go b/http/codegen/server_error_encoder_test.go
index c44fe66a..342437f4 100644
--- a/http/codegen/server_error_encoder_test.go
+++ b/http/codegen/server_error_encoder_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -34,8 +33,9 @@ func TestEncodeError(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 2)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 1)
diff --git a/http/codegen/server_handler_test.go b/http/codegen/server_handler_test.go
index ed857292..7967e6b7 100644
--- a/http/codegen/server_handler_test.go
+++ b/http/codegen/server_handler_test.go
@@ -9,7 +9,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/codegentest"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -26,8 +25,9 @@ func TestServerHandler(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			sections := codegentest.Sections(fs, filepath.Join("", "server.go"), "server-handler")
 			require.Greater(t, len(sections), 0)
 			code := codegen.SectionCode(t, sections[0])
diff --git a/http/codegen/server_init_test.go b/http/codegen/server_init_test.go
index e9225572..5069d6b8 100644
--- a/http/codegen/server_init_test.go
+++ b/http/codegen/server_init_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -31,8 +30,9 @@ func TestServerInit(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			require.Len(t, fs, c.FileCount)
 			sections := fs[0].SectionTemplates
 			require.Greater(t, len(sections), c.SectionNum)
diff --git a/http/codegen/server_mount_test.go b/http/codegen/server_mount_test.go
index 2285969d..6244f822 100644
--- a/http/codegen/server_mount_test.go
+++ b/http/codegen/server_mount_test.go
@@ -9,7 +9,6 @@ import (
 
 	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/codegen/codegentest"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -34,8 +33,9 @@ func TestServerMount(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles(genpkg, expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles(genpkg, services)
 			sections := codegentest.Sections(fs, filepath.Join("", "server.go"), c.SectionName)
 			require.Greater(t, len(sections), c.SectionNum)
 			code := codegen.SectionCode(t, sections[c.SectionNum])
diff --git a/http/codegen/server_payload_types_test.go b/http/codegen/server_payload_types_test.go
index 7767e0dc..29f21aca 100644
--- a/http/codegen/server_payload_types_test.go
+++ b/http/codegen/server_payload_types_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -121,9 +120,10 @@ func TestPayloadConstructor(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			require.Len(t, expr.Root.API.HTTP.Services, 1)
-			fs := serverType("", expr.Root.API.HTTP.Services[0], make(map[string]struct{}))
+			root := RunHTTPDSL(t, c.DSL)
+			require.Len(t, root.API.HTTP.Services, 1)
+			services := CreateHTTPServices(root)
+			fs := serverType("", root.API.HTTP.Services[0], make(map[string]struct{}), services)
 			sections := fs.SectionTemplates
 			var section *codegen.SectionTemplate
 			for _, s := range sections {
diff --git a/http/codegen/server_types_test.go b/http/codegen/server_types_test.go
index ead1819a..9c0a9a8d 100644
--- a/http/codegen/server_types_test.go
+++ b/http/codegen/server_types_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -36,8 +35,9 @@ func TestServerTypes(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := serverType(genpkg, expr.Root.API.HTTP.Services[0], make(map[string]struct{}))
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := serverType(genpkg, root.API.HTTP.Services[0], make(map[string]struct{}), services)
 			var buf bytes.Buffer
 			for _, s := range fs.SectionTemplates[1:] {
 				require.NoError(t, s.Write(&buf))
diff --git a/http/codegen/sse_server_test.go b/http/codegen/sse_server_test.go
index 118a3b07..28be65bd 100644
--- a/http/codegen/sse_server_test.go
+++ b/http/codegen/sse_server_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -28,8 +27,9 @@ func TestSSE(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := ServerFiles("", expr.Root)
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			fs := ServerFiles("", services)
 			require.Len(t, fs, 3)
 			sections := fs[1].SectionTemplates
 			require.Greater(t, len(sections), 1)
diff --git a/http/codegen/streaming_test.go b/http/codegen/streaming_test.go
index 415acaed..c542bddb 100644
--- a/http/codegen/streaming_test.go
+++ b/http/codegen/streaming_test.go
@@ -201,7 +201,10 @@ func TestServerStreaming(t *testing.T) {
 		}},
 	}
 
-	filesFn := func() []*codegen.File { return ServerFiles("", expr.Root) }
+	filesFn := func(root *expr.RootExpr) []*codegen.File {
+		services := CreateHTTPServices(root)
+		return ServerFiles("", services)
+	}
 	runTests(t, cases, filesFn)
 }
 
@@ -381,15 +384,18 @@ func TestClientStreaming(t *testing.T) {
 			{"client-websocket-recv", &testdata.BidirectionalStreamingUserTypeMapClientStreamRecvCode},
 		}},
 	}
-	filesFn := func() []*codegen.File { return ClientFiles("", expr.Root) }
+	filesFn := func(root *expr.RootExpr) []*codegen.File {
+		services := CreateHTTPServices(root)
+		return ClientFiles("", services)
+	}
 	runTests(t, cases, filesFn)
 }
 
-func runTests(t *testing.T, cases []*testCase, filesFn func() []*codegen.File) {
+func runTests(t *testing.T, cases []*testCase, filesFn func(root *expr.RootExpr) []*codegen.File) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			fs := filesFn()
+			root := RunHTTPDSL(t, c.DSL)
+			fs := filesFn(root)
 			require.Greater(t, len(fs), 1)
 			for _, s := range c.Sections {
 				var code string
diff --git a/http/codegen/testing.go b/http/codegen/testing.go
index b6d0a08e..317063a5 100644
--- a/http/codegen/testing.go
+++ b/http/codegen/testing.go
@@ -11,9 +11,13 @@ import (
 // RunHTTPDSL returns the HTTP DSL root resulting from running the given DSL.
 func RunHTTPDSL(t *testing.T, dsl func()) *expr.RootExpr {
 	// reset all roots and codegen data structures
-	service.Services = make(service.ServicesData)
-	HTTPServices = make(ServicesData)
-	return expr.RunDSL(t, dsl)
+	root := expr.RunDSL(t, dsl)
+	return root
+}
+
+// CreateHTTPServices creates a new ServicesData instance for testing.
+func CreateHTTPServices(root *expr.RootExpr) *ServicesData {
+	return NewServicesData(service.NewServicesData(root))
 }
 
 // makeGolden returns a file object used to write test expectations. If
diff --git a/http/codegen/transform_helper_test.go b/http/codegen/transform_helper_test.go
index 6f67d61c..5882435d 100644
--- a/http/codegen/transform_helper_test.go
+++ b/http/codegen/transform_helper_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"goa.design/goa/v3/codegen"
-	"goa.design/goa/v3/expr"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
 
@@ -23,8 +22,9 @@ func TestTransformHelperServer(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			f := serverEncodeDecodeFile("", expr.Root.API.HTTP.Services[0])
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			f := serverEncodeDecodeFile("", root.API.HTTP.Services[0], services)
 			sections := f.SectionTemplates
 			require.Greater(t, len(sections), c.Offset)
 			code := codegen.SectionCode(t, sections[len(sections)-c.Offset])
@@ -47,8 +47,9 @@ func TestTransformHelperCLI(t *testing.T) {
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
-			RunHTTPDSL(t, c.DSL)
-			f := clientEncodeDecodeFile("", expr.Root.API.HTTP.Services[0])
+			root := RunHTTPDSL(t, c.DSL)
+			services := CreateHTTPServices(root)
+			f := clientEncodeDecodeFile("", root.API.HTTP.Services[0], services)
 			sections := f.SectionTemplates
 			require.Greater(t, len(sections), c.Offset)
 			code := codegen.SectionCode(t, sections[len(sections)-c.Offset])