Metadata Catalog: Ontology-Driven Data Asset Discovery and Governance
The coomia-dip metadata catalog centers on the Ontology, integrating technical metadata (schema, statistics), business metadata (descriptions, tags, owners), and operational metadata (lineage, classification, quality metrics). The catalog provides unified search, data maps, impact analysis, and compliance views, accessible programmatically through gRPC API and SDK. This article covers the complete design from catalog architecture, metadata models, search engine, data maps, to governance workflows.
“Series: S6 Platform Engineering · Article 11 | Level: Advanced | Reading Time: 18 min
Metadata Catalog: Ontology-Driven Data Asset Discovery and Governance
#TL;DR
The coomia-dip metadata catalog centers on the Ontology, integrating technical metadata (schema, statistics), business metadata (descriptions, tags, owners), and operational metadata (lineage, classification, quality metrics). The catalog provides unified search, data maps, impact analysis, and compliance views, accessible programmatically through gRPC API and SDK. This article covers the complete design from catalog architecture, metadata models, search engine, data maps, to governance workflows.
#1. Core Value of the Metadata Catalog
#1.1 Data Discovery Challenges
As Ontology object types grow to hundreds or thousands, users face the "where is the data" dilemma:
- Discovery difficulty: Not knowing what data assets are available on the platform
- Understanding difficulty: Finding data but not understanding field meanings and business context
- Trust difficulty: Uncertainty about data quality, freshness, and reliability
- Compliance difficulty: Unable to quickly locate assets containing sensitive data
#1.2 Ontology-First Design
Unlike traditional metadata catalogs (Apache Atlas, DataHub), the coomia-dip catalog treats the Ontology as a first-class citizen:
┌──────────────────────────────────────────────┐
│ Metadata Catalog │
│ ┌────────────────────────────────────────┐ │
│ │ Ontology Layer │ │
│ │ ObjectTypes, Links, Actions │ │
│ │ (Core data model = metadata skeleton) │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌────────┐ ┌─────┴──────┐ ┌──────────────┐ │
│ │Technical│ │ Business │ │ Operational │ │
│ │Metadata │ │ Metadata │ │ Metadata │ │
│ │ │ │ │ │ │ │
│ │• Schema │ │• Descript. │ │• Lineage │ │
│ │• Stats │ │• Tags │ │• Classific. │ │
│ │• Partns │ │• Owners │ │• Quality │ │
│ │• Format │ │• Domains │ │• Usage stats │ │
│ └────────┘ └────────────┘ └──────────────┘ │
└──────────────────────────────────────────────┘
#1.3 Comparison with Palantir Foundry
| Capability | Palantir Foundry | coomia-dip |
|---|---|---|
| Metadata core | Dataset-centric | Ontology-centric |
| Search | Full-text search | Full-text + semantic search |
| Data map | Monocle | Built-in topology graph |
| Tag system | Supported | Hierarchical + auto-tagging |
| API | Internal | gRPC + SDK |
#2. Metadata Model
#2.1 Asset Model
class CatalogAsset(BaseModel):
"""Metadata catalog asset"""
asset_id: str = Field(description="Asset unique identifier")
asset_type: AssetType = Field(description="Asset type")
name: str
display_name: str
description: str = ""
namespace: str = "default"
# Business metadata
owner: AssetOwner
domain: str = Field(description="Business domain")
tags: list[Tag] = Field(default_factory=list)
glossary_terms: list[str] = Field(default_factory=list)
# Technical metadata
technical: TechnicalMetadata
# Operational metadata
operational: OperationalMetadata
# Security metadata
classification: ClassificationLevel
sensitivity_tags: list[str] = Field(default_factory=list)
# Quality scores
quality_score: float = Field(default=0.0, ge=0.0, le=1.0)
trust_score: float = Field(default=0.0, ge=0.0, le=1.0)
created_at: datetime
updated_at: datetime
last_accessed_at: datetime | None = None
class AssetType(str, Enum):
OBJECT_TYPE = "object_type"
LINK_TYPE = "link_type"
ACTION_TYPE = "action_type"
DATASET = "dataset"
PIPELINE = "pipeline"
DASHBOARD = "dashboard"
class TechnicalMetadata(BaseModel):
schema_version: int
properties: list[PropertyMetadata]
storage_format: str
partition_spec: dict | None = None
row_count: int | None = None
size_bytes: int | None = None
last_modified: datetime | None = None
class PropertyMetadata(BaseModel):
name: str
display_name: str
data_type: str
nullable: bool = True
description: str = ""
classification: ClassificationLevel | None = None
statistics: PropertyStatistics | None = None
class PropertyStatistics(BaseModel):
distinct_count: int | None = None
null_count: int | None = None
min_value: str | None = None
max_value: str | None = None
avg_length: float | None = None
sample_values: list[str] = Field(default_factory=list)
class OperationalMetadata(BaseModel):
lineage_available: bool = False
upstream_count: int = 0
downstream_count: int = 0
pipeline_ids: list[str] = Field(default_factory=list)
refresh_frequency: str | None = None
sla_target: str | None = None
last_refresh: datetime | None = None
access_count_30d: int = 0
unique_users_30d: int = 0
#2.2 Tag System
class Tag(BaseModel):
key: str
value: str | None = None
category: TagCategory = TagCategory.USER_DEFINED
source: TagSource = TagSource.MANUAL
confidence: float = 1.0
class TagCategory(str, Enum):
DOMAIN = "domain"
SENSITIVITY = "sensitivity"
LIFECYCLE = "lifecycle"
QUALITY = "quality"
COMPLIANCE = "compliance"
USER_DEFINED = "user_defined"
class TagSource(str, Enum):
MANUAL = "manual"
AUTO_CLASSIFIED = "auto_classified"
INHERITED = "inherited"
ML_SUGGESTED = "ml_suggested"
#3. Search Engine
#3.1 Full-Text Search
class CatalogSearchEngine:
"""Metadata catalog search engine"""
async def search(
self,
query: str,
filters: SearchFilters | None = None,
sort: SortSpec | None = None,
pagination: Pagination = Pagination(),
) -> SearchResult:
search_query = self._build_query(query)
if filters:
if filters.asset_types:
search_query = search_query.filter_by_types(filters.asset_types)
if filters.domains:
search_query = search_query.filter_by_domains(filters.domains)
if filters.classification_max:
search_query = search_query.filter_by_classification(
max_level=filters.classification_max,
)
if filters.tags:
search_query = search_query.filter_by_tags(filters.tags)
if filters.owner:
search_query = search_query.filter_by_owner(filters.owner)
results = await self._search_index.execute(
search_query,
offset=pagination.offset,
limit=pagination.limit,
)
return SearchResult(
total=results.total,
items=[self._to_search_hit(r) for r in results.hits],
facets=results.facets,
)
async def suggest(self, prefix: str, limit: int = 10) -> list[Suggestion]:
return await self._search_index.suggest(prefix, limit)
#3.2 Data Map
class DataMap:
"""Data map - visual data asset topology"""
async def get_domain_map(self, domain: str | None = None) -> DomainTopology:
assets = await self._catalog.list_assets(domain=domain)
nodes = []
edges = []
for asset in assets:
nodes.append(MapNode(
id=asset.asset_id,
label=asset.display_name,
type=asset.asset_type,
classification=asset.classification,
quality_score=asset.quality_score,
))
lineage = await self._lineage_query.get_downstream(asset.asset_id, max_depth=1)
for edge in lineage.edges:
edges.append(MapEdge(
source=edge.source_id,
target=edge.target_id,
type=edge.edge_type,
))
return DomainTopology(nodes=nodes, edges=edges, domain=domain)
async def get_asset_neighborhood(
self, asset_id: str, depth: int = 2,
) -> AssetNeighborhood:
upstream = await self._lineage_query.get_upstream(asset_id, max_depth=depth)
downstream = await self._lineage_query.get_downstream(asset_id, max_depth=depth)
return AssetNeighborhood(
center=asset_id,
upstream_graph=upstream,
downstream_graph=downstream,
)
#4. Data Quality Scoring
#4.1 Quality Dimensions
class DataQualityScorer:
"""Data quality scorer"""
DIMENSIONS = {
"completeness": 0.25,
"accuracy": 0.20,
"freshness": 0.20,
"consistency": 0.15,
"documentation": 0.10,
"accessibility": 0.10,
}
async def compute_score(self, asset: CatalogAsset) -> QualityReport:
scores = {}
scores["completeness"] = await self._score_completeness(asset)
scores["accuracy"] = await self._score_accuracy(asset)
scores["freshness"] = self._score_freshness(asset)
scores["consistency"] = await self._score_consistency(asset)
scores["documentation"] = self._score_documentation(asset)
scores["accessibility"] = self._score_accessibility(asset)
overall = sum(
scores[dim] * weight
for dim, weight in self.DIMENSIONS.items()
)
return QualityReport(
asset_id=asset.asset_id,
overall_score=overall,
dimension_scores=scores,
computed_at=datetime.utcnow(),
)
def _score_freshness(self, asset: CatalogAsset) -> float:
if not asset.operational.last_refresh:
return 0.0
age = datetime.utcnow() - asset.operational.last_refresh
if age < timedelta(hours=1):
return 1.0
elif age < timedelta(hours=24):
return 0.8
elif age < timedelta(days=7):
return 0.5
elif age < timedelta(days=30):
return 0.3
else:
return 0.1
def _score_documentation(self, asset: CatalogAsset) -> float:
score = 0.0
if asset.description:
score += 0.3
if asset.owner:
score += 0.2
documented = sum(1 for p in asset.technical.properties if p.description)
total = len(asset.technical.properties)
if total > 0:
score += 0.5 * (documented / total)
return score
#5. Governance Workflows
#5.1 Asset Certification
class AssetCertification:
"""Data asset certification"""
class CertificationLevel(str, Enum):
UNCERTIFIED = "uncertified"
BRONZE = "bronze" # Basic documentation complete
SILVER = "silver" # Quality checks passed
GOLD = "gold" # Fully governance compliant
CERTIFICATION_REQUIREMENTS = {
CertificationLevel.BRONZE: [
"has_description", "has_owner", "has_classification",
],
CertificationLevel.SILVER: [
"has_description", "has_owner", "has_classification",
"quality_score_above_0.7", "has_lineage",
],
CertificationLevel.GOLD: [
"has_description", "has_owner", "has_classification",
"quality_score_above_0.9", "has_lineage", "has_sla",
"all_properties_documented", "compliance_review_passed",
],
}
async def evaluate_certification(
self, asset: CatalogAsset,
) -> CertificationResult:
results = {}
for level in [
self.CertificationLevel.GOLD,
self.CertificationLevel.SILVER,
self.CertificationLevel.BRONZE,
]:
requirements = self.CERTIFICATION_REQUIREMENTS[level]
met = all(self._check_requirement(asset, req) for req in requirements)
results[level] = met
if results[self.CertificationLevel.GOLD]:
achieved = self.CertificationLevel.GOLD
elif results[self.CertificationLevel.SILVER]:
achieved = self.CertificationLevel.SILVER
elif results[self.CertificationLevel.BRONZE]:
achieved = self.CertificationLevel.BRONZE
else:
achieved = self.CertificationLevel.UNCERTIFIED
return CertificationResult(
asset_id=asset.asset_id, level=achieved, details=results,
)
#5.2 Ownership Management
class OwnershipManager:
"""Data asset ownership management"""
async def assign_owner(
self, asset_id: str, owner: AssetOwner, assigned_by: str,
) -> None:
asset = await self._catalog.get_asset(asset_id)
old_owner = asset.owner
asset.owner = owner
await self._catalog.update_asset(asset)
await self._audit.emit(AuditEvent(
event_type=AuditEventType.SCHEMA_CHANGE,
action="assign_owner",
changes=[AuditChange(
field="owner",
old_value=old_owner.model_dump_json() if old_owner else None,
new_value=owner.model_dump_json(),
change_type="update",
)],
))
async def find_orphan_assets(self) -> list[CatalogAsset]:
all_assets = await self._catalog.list_assets()
return [a for a in all_assets if not a.owner or a.owner.is_inactive]
#6. gRPC Service Interface
syntax = "proto3";
package onto.catalog.v1;
service CatalogService {
rpc SearchAssets(SearchRequest) returns (SearchResponse);
rpc GetAsset(GetAssetRequest) returns (CatalogAsset);
rpc ListAssets(ListAssetsRequest) returns (ListAssetsResponse);
rpc AddTag(AddTagRequest) returns (CatalogAsset);
rpc RemoveTag(RemoveTagRequest) returns (CatalogAsset);
rpc GetDataMap(DataMapRequest) returns (DataMapResponse);
rpc GetAssetNeighborhood(NeighborhoodRequest) returns (NeighborhoodResponse);
rpc GetQualityReport(QualityRequest) returns (QualityReport);
rpc GetCertification(CertificationRequest) returns (CertificationResult);
rpc AssignOwner(AssignOwnerRequest) returns (CatalogAsset);
rpc FindOrphanAssets(FindOrphansRequest) returns (ListAssetsResponse);
rpc Suggest(SuggestRequest) returns (SuggestResponse);
}
#7. Testing Strategy
class TestMetadataCatalog:
async def test_search_by_keyword(self):
results = await search_engine.search("employee salary")
assert len(results.items) > 0
assert any("Employee" in r.name for r in results.items)
async def test_search_with_filters(self):
results = await search_engine.search(
"customer",
filters=SearchFilters(
asset_types=[AssetType.OBJECT_TYPE],
classification_max=ClassificationLevel.CONFIDENTIAL,
),
)
for item in results.items:
assert item.classification <= ClassificationLevel.CONFIDENTIAL
async def test_quality_scoring(self):
scorer = DataQualityScorer()
report = await scorer.compute_score(well_documented_asset)
assert report.overall_score > 0.7
async def test_certification_levels(self):
cert = AssetCertification()
result = await cert.evaluate_certification(gold_asset)
assert result.level == AssetCertification.CertificationLevel.GOLD
async def test_find_orphan_assets(self):
manager = OwnershipManager(catalog)
orphans = await manager.find_orphan_assets()
for orphan in orphans:
assert not orphan.owner or orphan.owner.is_inactive
#8. Production Best Practices
#8.1 Metadata Collection
- Schema changes automatically trigger catalog updates
- Statistics collected daily on a scheduled basis
- Usage statistics collected via real-time streaming
- Lineage information automatically extracted from Pipeline executions
#8.2 Governance Process
- New assets must achieve Bronze certification within 7 days
- Business-facing assets should achieve Silver certification
- Critical business assets must achieve Gold certification
- Orphan assets checked monthly; unclaimed after 30 days are downgraded
#8.3 Search Optimization
- Establish domain-specific synonym tables
- Leverage usage statistics to boost popular assets in search rankings
- High quality-score assets are prioritized in search results
#9. Summary
The coomia-dip metadata catalog centers on the Ontology to deliver unified data asset discovery, understanding, and governance. Key design highlights:
- Ontology-First: Organizes metadata around object types, not traditional tables/files
- Three-layer metadata: Comprehensive coverage of technical, business, and operational dimensions
- Quality scoring: 6-dimension quantitative scoring system for objective data trustworthiness assessment
- Certification system: Bronze/Silver/Gold three-tier certification drives data governance
- Search engine: Full-text search + multi-dimensional filtering + auto-complete
The next article will explore the coomia-dip compliance design framework.