خريطة التبعية للأشعة تحت الحمراء
مردود التجميع على نطاق واسع. يتم تجميع بنية الخدمة في tiers، كل مجموعة قابلة للطي - بحيث تفتح الخريطة على شكل خمسة مربعات والمستوى العالي التبعيات بين منهم بدلاً من كرة الشعر. رابط بين خدمتين في يتم طي طبقات مختلفة في سهم واحد بين صناديقها؛ قم بتوسيع الطبقة للحفر في خدماتها وشاهد إعادة استهداف تلك الحواف مع فتح الصناديق. حواف موجهة إظهار مكالمات الخدمة التي (سمك يتتبع حركة المرور، علامات كهرمانية متقطعة غير متزامنة روابط قائمة الانتظار)، ويتتبع اللون الطبقة.
انقر فوق شارة + / − الخاصة بالمجموعة لتبديلها، أو استخدم الأزرار.
js
// import { ColorPaletteMapper } from 'pivotick'
// Colourblind-safe palette, one hue per tier (a tier box and its children share it).
const palette = new ColorPaletteMapper('okabe-ito')
const options = {
render: {
// Tiers are expandable out of the box: a +/- badge appears on each box and a
// click toggles it (see the Expand/collapse tab for the programmatic path).
enableNodeExpansion: true,
defaultNodeStyle: {
size: 22,
strokeColor: '#ffffff',
textColor: '#1e293b',
text: (node) => node.getData()?.label,
textVerticalShift: -1.8,
color: (node) => palette.getColor(node.getData()?.tier)
},
defaultEdgeStyle: {
curveStyle: 'straight',
markerEnd: 'arrow',
// Async links (to the queue / telemetry) flow as dashed amber.
strokeColor: (edge) => (edge.getData()?.async ? '#E69F00' : '#94a3b8'),
strokeWidth: (edge) => 1 + (edge.getData()?.weight ?? 1) * 0.6,
dashed: (edge) => !!edge.getData()?.async,
animateDash: true,
opacity: 0.85
}
},
simulation: {
// Gentle repulsion so the tier boxes spread a little without the
// weakly-linked ones drifting off.
d3ManyBodyStrength: -140,
d3LinkDistance: 55
}
}js
// Expand or collapse every tier at once. toggleExpandNode takes a Node instance,
// so resolve the id first; node.expanded tells you the current state.
const tiers = ['ingress', 'app', 'core', 'datastores', 'observability']
function setAllExpanded(graph, expanded) {
tiers.forEach((id) => {
const node = graph.getMutableNode(id)
if (node && !!node.expanded !== expanded) graph.toggleExpandNode(node)
})
}js
// A service dependency map where *every tier is a cluster* — a node with
// `children`. Collapsed, the map is just the tiers and the dependencies between
// them (box → box); a link between two services in different tiers folds into a
// single arrow between their boxes. Expand a tier to drill into its services and
// see their real edges re-target as the boxes open. All edges are declared once,
// at the top level, between the real services — the boxes are derived.
const data = {
nodes: [
{
id: 'ingress', data: { label: 'Ingress', tier: 'Ingress' }, expanded: false,
children: [
{ id: 'cdn', data: { label: 'CDN', tier: 'Ingress' } },
{ id: 'lb', data: { label: 'Load balancer', tier: 'Ingress' } }
]
},
{
id: 'app', data: { label: 'App tier', tier: 'App' }, expanded: false,
children: [
{ id: 'web', data: { label: 'Web app', tier: 'App' } },
{ id: 'gateway', data: { label: 'API gateway', tier: 'App' } },
{ id: 'admin', data: { label: 'Admin', tier: 'App' } }
]
},
{
id: 'core', data: { label: 'Core services', tier: 'Core' }, expanded: false,
children: [
{ id: 'auth', data: { label: 'Auth', tier: 'Core' } },
{ id: 'orders', data: { label: 'Orders', tier: 'Core' } },
{ id: 'payments', data: { label: 'Payments', tier: 'Core' } }
]
},
{
id: 'datastores', data: { label: 'Data stores', tier: 'Data' }, expanded: false,
children: [
{ id: 'postgres', data: { label: 'Postgres', tier: 'Data' } },
{ id: 'redis', data: { label: 'Redis', tier: 'Data' } },
{ id: 'kafka', data: { label: 'Kafka', tier: 'Data' } },
{ id: 'blob', data: { label: 'Blob store', tier: 'Data' } }
]
},
{
id: 'observability', data: { label: 'Observability', tier: 'Observability' }, expanded: false,
children: [
{ id: 'metrics', data: { label: 'Metrics', tier: 'Observability' } },
{ id: 'logs', data: { label: 'Logs', tier: 'Observability' } },
{ id: 'traces', data: { label: 'Traces', tier: 'Observability' } }
]
}
],
edges: [
// Ingress → App. `weight` thickens the link once the tiers are open.
{ from: 'cdn', to: 'web', data: { weight: 3 } },
{ from: 'lb', to: 'web', data: { weight: 3 } },
{ from: 'lb', to: 'gateway', data: { weight: 2 } },
// App → Core.
{ from: 'web', to: 'auth', data: { weight: 2 } },
{ from: 'web', to: 'orders', data: { weight: 3 } },
{ from: 'admin', to: 'auth', data: { weight: 1 } },
{ from: 'gateway', to: 'orders', data: { weight: 2 } },
{ from: 'gateway', to: 'payments', data: { weight: 2 } },
// Inside Core (only visible once the Core box is open).
{ from: 'orders', to: 'payments', data: { weight: 3 } },
// Core → Data stores.
{ from: 'orders', to: 'postgres', data: { weight: 3 } },
{ from: 'payments', to: 'postgres', data: { weight: 2 } },
{ from: 'auth', to: 'redis', data: { weight: 2 } },
{ from: 'web', to: 'blob', data: { weight: 1 } }, // App → Data stores
{ from: 'payments', to: 'kafka', data: { weight: 2, async: true } },
{ from: 'orders', to: 'kafka', data: { weight: 1, async: true } },
// → Observability (async telemetry).
{ from: 'orders', to: 'metrics', data: { weight: 1, async: true } },
{ from: 'payments', to: 'logs', data: { weight: 1, async: true } },
{ from: 'gateway', to: 'traces', data: { weight: 1, async: true } }
]
}