Repository: apigee/registry
Pull request #845 (2022-11-17 10:03:14)

Add the original resource to patterns.ResourceInstance
This the preparation for enabling the following feature in the scoring tool:

Currently, a score can be derived only from the artifacts that are attached to the primary entities (apis, versions, specs, deployments). However, we would also like the ability to derive a score from the primary entity itself, an example of this is the completeness score for an API, how well hydrated (with information) is a particular API stored in the registry. 

codecov[bot]: # [Codecov](https://codecov.io/gh/apigee/registry/pull/845?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) Report
> Merging [#845](https://codecov.io/gh/apigee/registry/pull/845?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (944b66c) into [main](https://codecov.io/gh/apigee/registry/commit/fa370d3032231142375fc67d124f06dfa610309d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (fa370d3) will **increase** coverage by `0.02%`.
> The diff coverage is `10.86%`.

```diff
@@            Coverage Diff             @@
##             main     #845      +/-   ##
==========================================
+ Coverage   56.92%   56.94%   +0.02%     
==========================================
  Files          97       97              
  Lines        8923     8908      -15     
==========================================
- Hits         5079     5073       -6     
+ Misses       3323     3317       -6     
+ Partials      521      518       -3     
```


| [Impacted Files](https://codecov.io/gh/apigee/registry/pull/845?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) | Coverage Δ | |
|---|---|---|
| [cmd/registry/patterns/resources.go](https://codecov.io/gh/apigee/registry/pull/845/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L3BhdHRlcm5zL3Jlc291cmNlcy5nbw==) | `20.60% <0.00%> (-0.29%)` | :arrow_down: |
| [cmd/registry/controller/list.go](https://codecov.io/gh/apigee/registry/pull/845/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NvbnRyb2xsZXIvbGlzdC5nbw==) | `67.16% <66.66%> (+7.86%)` | :arrow_up: |
| [cmd/registry/controller/controller.go](https://codecov.io/gh/apigee/registry/pull/845/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NvbnRyb2xsZXIvY29udHJvbGxlci5nbw==) | `76.34% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee)

theganyo: I'm likely missing something, but it appears that the ResourceInstance implementations (eg. SpecResource, ApiResource, etc) are now wrappers without a purpose?
shrutiparabgoogle: > I'm likely missing something, but it appears that the ResourceInstance implementations (eg. SpecResource, ApiResource, etc) are now wrappers without a purpose?

The `ResourceInstance` is the abstraction which the controller and also the scoring commands use to represent the resources in the registry. For these commands, everything is an instance of `ResourceInstance` in the registry and they are only interested in their timestamps and names (for now). 

These wrappers make sure that the different resource types in the registry can be represented as the `ResourceInstance`s for these commands. 

Now that we are interested in the actual resource as well (only in a few occasions), this change is storing the original resource in these wrappers but still behaves as a `ResourceInstance`.  

Does this explanation help? I am open to other implementation approaches that I might have missed.
theganyo: > > I'm likely missing something, but it appears that the ResourceInstance implementations (eg. SpecResource, ApiResource, etc) are now wrappers without a purpose?
> 
> Does this explanation help? I am open to other implementation approaches that I might have missed.

Thanks. I understood what was happening and the need for a common interface, I just didn't see a need for the wrappers... the key bit of functionality that this provides seems to be the ResourceName implementations. I was imagining this could've simply been supported directly in the names package instead of an additional set of wrappers. But perhaps not.
shrutiparabgoogle: > I was imagining this could've simply been supported directly in the names package instead of an additional set of wrappers. But perhaps not.

I agree, I imagine eventually this interface going into the names package, but it still needs to mature out of specific use-cases and be generic enough to be included in names package. I am sure there will be a refactor on this part of the code, but that is outside the scope of this PR.


shrutiparabgoogle: > > I was imagining this could've simply been supported directly in the names package instead of an additional set of wrappers. But perhaps not.
> 
> I agree, I imagine eventually this interface going into the names package, but it still needs to mature out of specific use-cases and be generic enough to be included in the `names` package.
> 
> The `names` package was developed after the introduction of the `ResourceName` and `ResourceInstance` interfaces. And the interfaces have changed a lot since then, we should look at consolidating the `names` package and `resource.go` in the future, thanks for calling this out.

Here is an issue for future scope: https://github.com/apigee/registry/issues/848

Patch:
diff --git a/cmd/registry/controller/controller.go b/cmd/registry/controller/controller.go
index 3235814..c2131e9 100644
--- a/cmd/registry/controller/controller.go
+++ b/cmd/registry/controller/controller.go
@@ -286,7 +286,7 @@ func generateCreateActions(
 		}
 		parentList = []patterns.ResourceInstance{
 			patterns.ProjectResource{
-				ProjectName: parentName,
+				ProjectName: parentName.String(),
 			},
 		}
 
diff --git a/cmd/registry/controller/list.go b/cmd/registry/controller/list.go
index 0b31ee7..8881aca 100644
--- a/cmd/registry/controller/list.go
+++ b/cmd/registry/controller/list.go
@@ -49,7 +49,7 @@ func listResources(ctx context.Context, client listingClient, pattern, filter st
 	} else if spec, err := names.ParseSpecCollection(pattern); err == nil {
 		err2 = client.ListSpecs(ctx, spec, filter, generateSpecHandler(&result))
 	} else if artifact, err := names.ParseArtifactCollection(pattern); err == nil {
-		err2 = client.ListArtifacts(ctx, artifact, filter, false, generateArtifactHandler(&result))
+		err2 = client.ListArtifacts(ctx, artifact, filter, true, generateArtifactHandler(&result))
 	}
 
 	// Then try to match resource names.
@@ -60,7 +60,7 @@ func listResources(ctx context.Context, client listingClient, pattern, filter st
 	} else if spec, err := names.ParseSpec(pattern); err == nil {
 		err2 = client.ListSpecs(ctx, spec, filter, generateSpecHandler(&result))
 	} else if artifact, err := names.ParseArtifact(pattern); err == nil {
-		err2 = client.ListArtifacts(ctx, artifact, filter, false, generateArtifactHandler(&result))
+		err2 = client.ListArtifacts(ctx, artifact, filter, true, generateArtifactHandler(&result))
 	}
 
 	if err2 != nil {
@@ -71,14 +71,8 @@ func listResources(ctx context.Context, client listingClient, pattern, filter st
 
 func generateApiHandler(result *[]patterns.ResourceInstance) func(*rpc.Api) error {
 	return func(api *rpc.Api) error {
-		name, err := names.ParseApi(api.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), patterns.ApiResource{
-			ApiName:   patterns.ApiName{Name: name},
-			Timestamp: api.UpdateTime.AsTime(),
+			Api: api,
 		})
 
 		return nil
@@ -87,14 +81,8 @@ func generateApiHandler(result *[]patterns.ResourceInstance) func(*rpc.Api) erro
 
 func generateVersionHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiVersion) error {
 	return func(version *rpc.ApiVersion) error {
-		name, err := names.ParseVersion(version.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), patterns.VersionResource{
-			VersionName: patterns.VersionName{Name: name},
-			Timestamp:   version.UpdateTime.AsTime(),
+			Version: version,
 		})
 
 		return nil
@@ -103,17 +91,8 @@ func generateVersionHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiVe
 
 func generateSpecHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiSpec) error {
 	return func(spec *rpc.ApiSpec) error {
-		name, err := names.ParseSpec(spec.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), patterns.SpecResource{
-			SpecName: patterns.SpecName{
-				Name:       name,
-				RevisionID: spec.RevisionId,
-			},
-			Timestamp: spec.RevisionUpdateTime.AsTime(),
+			Spec: spec,
 		})
 
 		return nil
@@ -122,14 +101,8 @@ func generateSpecHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiSpec)
 
 func generateArtifactHandler(result *[]patterns.ResourceInstance) func(*rpc.Artifact) error {
 	return func(artifact *rpc.Artifact) error {
-		name, err := names.ParseArtifact(artifact.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), patterns.ArtifactResource{
-			ArtifactName: patterns.ArtifactName{Name: name},
-			Timestamp:    artifact.UpdateTime.AsTime(),
+			Artifact: artifact,
 		})
 
 		return nil
diff --git a/cmd/registry/patterns/resources.go b/cmd/registry/patterns/resources.go
index 48b5b1f..51a08bc 100644
--- a/cmd/registry/patterns/resources.go
+++ b/cmd/registry/patterns/resources.go
@@ -342,68 +342,89 @@ type ResourceInstance interface {
 }
 
 type SpecResource struct {
-	SpecName  ResourceName
-	Timestamp time.Time
+	Spec *rpc.ApiSpec
 }
 
 func (s SpecResource) UpdateTimestamp() time.Time {
-	return s.Timestamp
+	return s.Spec.RevisionUpdateTime.AsTime()
 }
 
 func (s SpecResource) ResourceName() ResourceName {
-	return s.SpecName
+	name, err := names.ParseSpecRevision(s.Spec.GetName())
+	if err != nil {
+		return nil
+	}
+	return SpecName{
+		Name:       name.Spec(),
+		RevisionID: s.Spec.RevisionId,
+	}
 }
 
 type VersionResource struct {
-	VersionName ResourceName
-	Timestamp   time.Time
+	Version *rpc.ApiVersion
 }
 
 func (v VersionResource) UpdateTimestamp() time.Time {
-	return v.Timestamp
+	return v.Version.UpdateTime.AsTime()
 }
 
 func (v VersionResource) ResourceName() ResourceName {
-	return v.VersionName
+	name, err := names.ParseVersion(v.Version.GetName())
+	if err != nil {
+		return nil
+	}
+	return VersionName{Name: name}
 }
 
 type ApiResource struct {
-	ApiName   ResourceName
-	Timestamp time.Time
+	Api *rpc.Api
 }
 
 func (a ApiResource) UpdateTimestamp() time.Time {
-	return a.Timestamp
+	return a.Api.UpdateTime.AsTime()
 }
 
 func (a ApiResource) ResourceName() ResourceName {
-	return a.ApiName
+	name, err := names.ParseApi(a.Api.GetName())
+	if err != nil {
+		return nil
+	}
+	return ApiName{Name: name}
 }
 
+// Project is a special resource which is not available through the registry client.
+// Hence we won't store the actual instance of the rpc.Project but instead only store the project name.
+// ProjectResource is mainly used to identify by name when we derive the parents of certain artifacts.
 type ProjectResource struct {
-	ProjectName ResourceName
-	Timestamp   time.Time
+	ProjectName string
 }
 
 func (p ProjectResource) UpdateTimestamp() time.Time {
-	return p.Timestamp
+	return time.Time{}
 }
 
 func (p ProjectResource) ResourceName() ResourceName {
-	return p.ProjectName
+	name, err := names.ParseProject(p.ProjectName)
+	if err != nil {
+		return nil
+	}
+	return ProjectName{Name: name}
 }
 
 type ArtifactResource struct {
-	ArtifactName ResourceName
-	Timestamp    time.Time
+	Artifact *rpc.Artifact
 }
 
 func (ar ArtifactResource) UpdateTimestamp() time.Time {
-	return ar.Timestamp
+	return ar.Artifact.UpdateTime.AsTime()
 }
 
 func (ar ArtifactResource) ResourceName() ResourceName {
-	return ar.ArtifactName
+	name, err := names.ParseArtifact(ar.Artifact.GetName())
+	if err != nil {
+		return nil
+	}
+	return ArtifactName{Name: name}
 }
 
 func ListResources(ctx context.Context, client connection.RegistryClient, pattern, filter string) ([]ResourceInstance, error) {
@@ -420,7 +441,7 @@ func ListResources(ctx context.Context, client connection.RegistryClient, patter
 	} else if rev, err := names.ParseSpecRevisionCollection(pattern); err == nil {
 		err2 = core.ListSpecRevisions(ctx, client, rev, filter, generateSpecHandler(&result))
 	} else if artifact, err := names.ParseArtifactCollection(pattern); err == nil {
-		err2 = core.ListArtifacts(ctx, client, artifact, filter, false, generateArtifactHandler(&result))
+		err2 = core.ListArtifacts(ctx, client, artifact, filter, true, generateArtifactHandler(&result))
 	}
 
 	// Then try to match resource names.
@@ -433,7 +454,7 @@ func ListResources(ctx context.Context, client connection.RegistryClient, patter
 	} else if rev, err := names.ParseSpecRevision(pattern); err == nil {
 		err2 = core.ListSpecRevisions(ctx, client, rev, filter, generateSpecHandler(&result))
 	} else if artifact, err := names.ParseArtifact(pattern); err == nil {
-		err2 = core.ListArtifacts(ctx, client, artifact, filter, false, generateArtifactHandler(&result))
+		err2 = core.ListArtifacts(ctx, client, artifact, filter, true, generateArtifactHandler(&result))
 	}
 
 	if err2 != nil {
@@ -445,14 +466,8 @@ func ListResources(ctx context.Context, client connection.RegistryClient, patter
 
 func generateApiHandler(result *[]ResourceInstance) func(*rpc.Api) error {
 	return func(api *rpc.Api) error {
-		name, err := names.ParseApi(api.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), ApiResource{
-			ApiName:   ApiName{Name: name},
-			Timestamp: api.UpdateTime.AsTime(),
+			Api: api,
 		})
 		return nil
 	}
@@ -460,13 +475,8 @@ func generateApiHandler(result *[]ResourceInstance) func(*rpc.Api) error {
 
 func generateVersionHandler(result *[]ResourceInstance) func(*rpc.ApiVersion) error {
 	return func(version *rpc.ApiVersion) error {
-		name, err := names.ParseVersion(version.GetName())
-		if err != nil {
-			return err
-		}
 		(*result) = append((*result), VersionResource{
-			VersionName: VersionName{Name: name},
-			Timestamp:   version.UpdateTime.AsTime(),
+			Version: version,
 		})
 		return nil
 	}
@@ -474,17 +484,8 @@ func generateVersionHandler(result *[]ResourceInstance) func(*rpc.ApiVersion) er
 
 func generateSpecHandler(result *[]ResourceInstance) func(*rpc.ApiSpec) error {
 	return func(spec *rpc.ApiSpec) error {
-		name, err := names.ParseSpecRevision(spec.GetName())
-		if err != nil {
-			return err
-		}
-
 		(*result) = append((*result), SpecResource{
-			SpecName: SpecName{
-				Name:       name.Spec(),
-				RevisionID: name.RevisionID,
-			},
-			Timestamp: spec.RevisionUpdateTime.AsTime(),
+			Spec: spec,
 		})
 		return nil
 	}
@@ -492,13 +493,8 @@ func generateSpecHandler(result *[]ResourceInstance) func(*rpc.ApiSpec) error {
 
 func generateArtifactHandler(result *[]ResourceInstance) func(*rpc.Artifact) error {
 	return func(artifact *rpc.Artifact) error {
-		name, err := names.ParseArtifact(artifact.GetName())
-		if err != nil {
-			return err
-		}
 		(*result) = append((*result), ArtifactResource{
-			ArtifactName: ArtifactName{Name: name},
-			Timestamp:    artifact.UpdateTime.AsTime(),
+			Artifact: artifact,
 		})
 		return nil
 	}


Test patch:
diff --git a/cmd/registry/scoring/score_test.go b/cmd/registry/scoring/score_test.go
index 30b1208..43ed64a 100644
--- a/cmd/registry/scoring/score_test.go
+++ b/cmd/registry/scoring/score_test.go
@@ -21,7 +21,6 @@ import (
 	"github.com/apigee/registry/cmd/registry/patterns"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/rpc"
-	"github.com/apigee/registry/server/registry/names"
 	"github.com/apigee/registry/server/registry/test/seeder"
 	metrics "github.com/google/gnostic/metrics"
 	"github.com/google/go-cmp/cmp"
@@ -427,13 +426,8 @@ func TestCalculateScore(t *testing.T) {
 			}
 
 			resource := patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			}
 
@@ -526,13 +520,8 @@ func TestProcessScoreFormula(t *testing.T) {
 		ScoreExpression: "size(files[0].problems)",
 	}
 	resource := patterns.SpecResource{
-		SpecName: patterns.SpecName{
-			Name: names.Spec{
-				ProjectID: "score-formula-test",
-				ApiID:     "petstore",
-				VersionID: "1.0.0",
-				SpecID:    "openapi.yaml",
-			},
+		Spec: &rpc.ApiSpec{
+			Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 		},
 	}
 
@@ -574,13 +563,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 				ScoreExpression: "size(files[0].problems)",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -598,13 +582,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 				ScoreExpression: "size(files[0].problems)",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -622,13 +601,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 				ScoreExpression: "size(files[0].problems)",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -648,13 +622,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 			},
 			formula: &rpc.ScoreFormula{},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -686,13 +655,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 				ScoreExpression: "size(files[0].problem)", // invalid expression
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -723,13 +687,8 @@ func TestProcessScoreFormulaError(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -854,13 +813,8 @@ func TestProcessRollUpFormula(t *testing.T) {
 		RollupExpression: "double(numErrors)/numOperations",
 	}
 	resource := patterns.SpecResource{
-		SpecName: patterns.SpecName{
-			Name: names.Spec{
-				ProjectID: "rollup-formula-test",
-				ApiID:     "petstore",
-				VersionID: "1.0.0",
-				SpecID:    "openapi.yaml",
-			},
+		Spec: &rpc.ApiSpec{
+			Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 		},
 	}
 
@@ -898,13 +852,8 @@ func TestProcessRollUpFormulaError(t *testing.T) {
 				RollupExpression: "double(numErrors)/numOperations",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -934,13 +883,8 @@ func TestProcessRollUpFormulaError(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -1000,13 +944,8 @@ func TestProcessRollUpFormulaError(t *testing.T) {
 				RollupExpression: "double(numErrors)/numOperations",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -1066,13 +1005,8 @@ func TestProcessRollUpFormulaError(t *testing.T) {
 				RollupExpression: "numError/numOperation", // should be numErrors/numOperations
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
@@ -1103,13 +1037,8 @@ func TestProcessRollUpFormulaError(t *testing.T) {
 				RollupExpression: "num-errors/num-operations",
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 		},
diff --git a/cmd/registry/scoring/scorecard_test.go b/cmd/registry/scoring/scorecard_test.go
index eba1039..82ca608 100644
--- a/cmd/registry/scoring/scorecard_test.go
+++ b/cmd/registry/scoring/scorecard_test.go
@@ -21,7 +21,6 @@ import (
 	"github.com/apigee/registry/cmd/registry/patterns"
 	"github.com/apigee/registry/pkg/connection"
 	"github.com/apigee/registry/rpc"
-	"github.com/apigee/registry/server/registry/names"
 	"github.com/apigee/registry/server/registry/test/seeder"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
@@ -517,13 +516,8 @@ func TestCalculateScoreCard(t *testing.T) {
 			}
 
 			resource := patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-card-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-card-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			}
 
@@ -646,13 +640,8 @@ func TestProcessScorePatterns(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -765,13 +754,8 @@ func TestProcessScorePatterns(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -884,13 +868,8 @@ func TestProcessScorePatterns(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -1003,13 +982,8 @@ func TestProcessScorePatterns(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -1240,13 +1214,8 @@ func TestProcessScorePatternsError(t *testing.T) {
 			}
 
 			resource := patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			}
 
diff --git a/cmd/registry/scoring/scoring-timestamp_test.go b/cmd/registry/scoring/scoring-timestamp_test.go
index ff69ade..deb2f16 100644
--- a/cmd/registry/scoring/scoring-timestamp_test.go
+++ b/cmd/registry/scoring/scoring-timestamp_test.go
@@ -280,13 +280,8 @@ func TestTimestampCalculateScore(t *testing.T) {
 			}
 
 			resource := patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			}
 
@@ -361,13 +356,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -409,13 +399,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -453,13 +438,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -499,13 +479,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -545,13 +520,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -591,13 +561,8 @@ func TestProcessScoreFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -691,13 +656,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -749,13 +709,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -810,13 +765,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: true,
@@ -868,13 +818,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -926,13 +871,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -987,13 +927,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -1048,13 +983,8 @@ func TestProcessRollUpFormulaTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "rollup-formula-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/rollup-formula-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -1195,13 +1125,8 @@ func TestProcessScorePatternsTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,
@@ -1287,13 +1212,8 @@ func TestProcessScorePatternsTimestamp(t *testing.T) {
 				},
 			},
 			resource: patterns.SpecResource{
-				SpecName: patterns.SpecName{
-					Name: names.Spec{
-						ProjectID: "score-patterns-test",
-						ApiID:     "petstore",
-						VersionID: "1.0.0",
-						SpecID:    "openapi.yaml",
-					},
+				Spec: &rpc.ApiSpec{
+					Name: "projects/score-patterns-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
 				},
 			},
 			takeAction: false,