Skip to content
bitzorcas
中EN

Recipe

Practical Guide: Frontend Integration & Metadata Hydration

Master frontend integration in BitzOrcas.Modern: OpenAPI TypeScript SDK generation, IReadModelMetaMapped projection, 10-stage pipeline Stage 10 dictionary and entity hydration, and React 19 best practices.

Last updated

In modern enterprise administration dashboards, tables and forms frequently require displaying resolved associated names (e.g. ClientId resolved to a legal entity name, StatusCode resolved to a localized dictionary tag, and UserId resolved to a lead counsel’s display name). Traditional architectures force teams into a painful dilemma:

  1. Forcing Backend Queries to Execute 5 to 10 LEFT JOINs: Results in bloated SQL queries, severe query latency regressions, and ruptures the physical boundary between modular monolith bounded contexts;
  2. Having the Frontend Fire N+1 Asynchronous Requests: Triggers severe Cumulative Layout Shift (CLS) on the client while overwhelming backend gateways under concurrent load.

BitzOrcas.Modern establishes the golden pattern: “Narrow Data Transfer + Stage 10 Automated Pipeline Metadata Hydration (ReadModelDisplayPipelineBehavior)”:

  • Clean Backend Queries: Handlers query solely the aggregate root’s core columns without cross-boundary database joins;
  • Intercepted Batch Hydration: The final stage of the 10-tier application pipeline (Stage 10) intercepts query results and performs batched, de-duplicated Redis lookups for referenced entity names and dictionary tags, appending them to the response envelope;
  • Zero-Boilerplate Client Consumption: Using @bitz/platform-sdk and @bitz/widgets, table components render resolved titles directly via __meta projections without manual mapping lookups.

This guide demonstrates how frontend integration and metadata hydration function through a real-world LegalTech scenario: “Civil Litigation Matter Intake Table with Automated Client and Status Hydration.”

Metadata Hydration Architecture Sequence

"Redis 7 (:6379 Dictionary & Master Data)""10-Stage Pipeline (Stage 10 ReadModelDisplay)""BitzOrcas.Api (:6881)""YARP Gateway (:6880)""Redis 7 (:6379 Dictionary & Master Data)""10-Stage Pipeline (Stage 10 ReadModelDisplay)""BitzOrcas.Api (:6881)""YARP Gateway (:6880)"Frontend ServerDataTable renders display names seamlessly via DataTableObjectCell"React 19 ServerDataTable"1. GET /api/legal/matters (Fetch paginated matters)12. Forward request to API Host23. Query Handler executes (returns clean DTOs containing ClientId, Status)34. ReadModelDisplayPipelineBehavior intercepts PagedResult<T>45. Extract all ClientIds and Status codes, batch-fetch from Redis56. Construct companion Meta dictionary and attach to payload67. Return complete JSON with __meta projections7"React 19 ServerDataTable"

Step 1: DTO Implements IReadModelMetaMapped Contract

Implement IReadModelMetaMapped on the query DTO to declare that this read model supports row-level metadata companion hydration:

src/Modules/Legal/BitzOrcas.Modules.Legal.Contracts/Matters/MatterListItemDto.cs
using System.Collections.Generic;
using BitzOrcas.Application.Abstractions.Queries;
using BitzOrcas.Domain.Results;
namespace BitzOrcas.Modules.Legal.Contracts.Matters;
/// <summary>
/// Matter list item read-only Data Transfer Object
/// </summary>
/// <remarks>
/// <para>Encapsulates core list columns compactly. Wide database joins are strictly forbidden.</para>
/// <para>Implements <see cref="IReadModelMetaMapped"/> to receive automated Stage 10 pipeline hydration.</para>
/// </remarks>
public sealed record MatterListItemDto : IReadModelMetaMapped
{
/// <summary>
/// Unique matter identifier
/// </summary>
public string Id { get; init; } = string.Empty;
/// <summary>
/// Business tracking code
/// </summary>
public string MatterCode { get; init; } = string.Empty;
/// <summary>
/// Case title
/// </summary>
public string Title { get; init; } = string.Empty;
/// <summary>
/// Retained client identifier
/// </summary>
public string ClientId { get; init; } = string.Empty;
/// <summary>
/// Lead counsel identifier
/// </summary>
public string LeadLawyerId { get; init; } = string.Empty;
/// <summary>
/// Matter status code (Draft, PendingReview, Active, Closed)
/// </summary>
public string Status { get; init; } = string.Empty;
/// <summary>
/// Litigation claim amount in CNY
/// </summary>
public decimal ClaimAmount { get; init; }
/// <summary>
/// Row-level companion entity dictionary (populated automatically by the pipeline)
/// </summary>
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, EntityRef>>? Meta { get; init; }
/// <summary>
/// Contract implementation: returns a clone containing hydrated row metadata
/// </summary>
object IReadModelMetaMapped.WithMeta(
IReadOnlyDictionary<string, IReadOnlyDictionary<string, EntityRef>> meta)
=> this with { Meta = meta };
}

Step 2: Configure Module-Level DisplayMap Rules

Register hydration mappings within module configuration, instructing the pipeline how to resolve foreign IDs into human-readable display values:

src/Modules/Legal/BitzOrcas.Modules.Legal/LegalDisplayMapConfig.cs
using BitzOrcas.Application.DisplayMap;
using BitzOrcas.Modules.Legal.Contracts.Matters;
using Microsoft.Extensions.DependencyInjection;
namespace BitzOrcas.Modules.Legal;
/// <summary>
/// Legal module metadata hydration mapping configuration
/// </summary>
public static class LegalDisplayMapConfig
{
/// <summary>
/// Registers metadata hydration rules for legal models
/// </summary>
/// <param name="services">DI service collection.</param>
public static IServiceCollection AddLegalDisplayMaps(this IServiceCollection services)
{
services.AddReadModelDisplayMap<MatterListItemDto>(map =>
{
// 1. Hydrate ClientId into client company name and credit code from master data
map.MapField(x => x.ClientId)
.FromProvider("ClientMasterData", key => $"MasterData:Client:{key}");
// 2. Hydrate LeadLawyerId into lawyer full name from identity profiles
map.MapField(x => x.LeadLawyerId)
.FromProvider("UserProfile", key => $"Identity:User:{key}");
// 3. Hydrate Status code into localized dictionary labels
map.MapDictionary(x => x.Status)
.FromCategory("Legal.MatterStatus");
});
return services;
}
}

Step 3: Frontend React 19 Consumes Hydrated Metadata

The backend API returns a standardized JSON payload where companion display names are mapped into __meta:

{
"items": [
{
"id": "MAT-2026-0001",
"matterCode": "CIV-2026-0081",
"title": "Cross-Border Semiconductor Patent Infringement",
"clientId": "CLI-88902",
"leadLawyerId": "USR-1002",
"status": "Active",
"claimAmount": 58000000.00,
"__meta": {
"clientName": "VeriSilicon Microelectronics Co., Ltd.",
"clientUnifiedSocialCreditCode": "9131000076210088X",
"leadLawyerName": "Attorney Xiaoming Zhang",
"statusDisplayText": "Active Trial"
}
}
],
"pageIndex": 1,
"pageSize": 20,
"totalCount": 1
}

On the frontend, @bitz/widgets table columns consume __meta projections via DataTableObjectCell, rendering enriched data with zero custom lookup logic:

frontend/apps/app/src/pages/legal/matters/matter-list-table.tsx
import { type JSX } from 'react';
import {
DataTableObjectCell,
ServerDataTable,
StatusBadge,
type DataTableColumn,
} from '@bitz/widgets';
import type { LegalMatterSummaryDto } from '@bitz/platform-sdk';
/**
* Matter list table column configuration
*/
export const legalMatterColumns: DataTableColumn<LegalMatterSummaryDto>[] = [
{
id: 'matterCode',
header: 'Tracking Code',
cell: (row) => (
<span className="font-mono text-sm font-semibold tracking-tight">
{row.matterCode}
</span>
),
width: '180px',
},
{
id: 'title',
header: 'Case Title',
cell: (row) => (
<span className="font-medium text-neutral-900 dark:text-neutral-100">
{row.title}
</span>
),
},
{
id: 'client',
header: 'Client / Retainer',
cell: (row) => (
// Core: Zero-latency resolution from row __meta, falling back to raw ClientId
<DataTableObjectCell
primaryText={row.__meta?.clientName ?? row.clientId}
secondaryText={row.__meta?.clientUnifiedSocialCreditCode}
/>
),
width: '240px',
},
{
id: 'leadLawyer',
header: 'Lead Counsel',
cell: (row) => (
<span className="text-sm text-neutral-700 dark:text-neutral-300">
{row.__meta?.leadLawyerName ?? row.leadLawyerId}
</span>
),
width: '140px',
},
{
id: 'status',
header: 'Status',
cell: (row) => (
<StatusBadge
tone={row.status === 'Active' ? 'success' : 'neutral'}
label={row.__meta?.statusDisplayText ?? row.status}
/>
),
width: '130px',
},
];

Summary

The automated metadata hydration architecture delivers substantial engineering dividends:

  • Architectural Decoupling with Blazing Query Speed: Backends execute simple single-table queries without cross-domain LEFT JOINs, improving indexed query performance by 300%–500%;
  • Batched In-Memory Pipeline Lookups: Stage 10 batches and de-duplicates entity lookups into a single multi-key Redis call, minimizing network round-trips;
  • Developer Simplicity: Frontend components render foreign keys without custom dictionary translation functions, enjoying clean, strongly-typed UI delivery.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%