Pular para o conteúdo

Predicados de visão

As visões não são estáticas: elas são geradas a partir do modelo. Qualquer alteração no modelo é aplicada imediatamente e atualiza as visões. Dois tipos de predicados definem o que fica visível: predicados de elementos e de relacionamentos.

Predicados de elementos definem explicitamente quais elementos ficam visíveis. Cada elemento incluído traz seus relacionamentos com os elementos que já estão visíveis.

view {
// Only backend is visible
include backend
// Add frontend to the view
// and its relationships with backend
include frontend
// Add authService to the view
// and its relationships with visible (backend and frontend)
include authService
// Add children of messageBroker,
// and their relationships among themselves and visible (backend, frontend and authService)
include messageBroker.*
// Add all descendants of messageBroker,
// and their relationships among themselves and visible (backend, frontend and authService)
include messageBroker.**
// Exclude emailsQueue and its relationships
exclude messageBroker.emailsQueue
}

Predicados podem ser combinados. O exemplo abaixo é equivalente ao anterior:

view {
include
backend,
frontend,
authService,
messageBroker.**
exclude messageBroker.emailsQueue
}

Predicados curinga podem ser usados para referenciar “tudo” (mas o comportamento difere entre visões com e sem escopo).
Considere o seguinte modelo:

model {
actor customer {
-> webApp 'uses in browser via HTTPS'
}
system cloud {
container backend {
component api
}
container ui {
component webApp {
-> api 'requests data'
}
}
}
}
views {
// Unscoped view - wildcard refers to top-level elements
view {
include *
// Visible top-level elements: customer, cloud
// and derived relationship customer -> cloud
}
// Scoped view - wildcard refers to element and its children
view of cloud.ui {
include *
// Visible:
// - cloud.ui
// - cloud.ui.webApp
// - customer
// - relationship customer -> cloud.ui.webApp
// - cloud.backend
// - cloud.ui.webApp -> cloud.backend, derived from cloud.ui.webApp -> cloud.backend.api
}
}

Você pode modificar propriedades de um elemento especificamente para a visão:

// Include the element and override its properties
include cloud.backend with {
title 'Backend components'
description '...'
technology 'Java, Spring'
icon tech:java
color amber
shape browser
multiple true
}
// Include all nested elements, change color and textSize
include cloud.* with {
color amber
textSize small
}

with pode ser usado apenas dentro de include.

Você pode definir navegação e links personalizados entre visões:

example.c4
view view2 {
include *
include cloud.backend with {
// navigate to 'view3' on click
navigateTo view3
}
}
view view3 {
include *
include cloud.backend with {
// navigate back to 'view2'
navigateTo view2
}
}
// elements by kind
include element.kind != system
exclude element.kind = container
// elements by tag
include element.tag != #V2
exclude element.tag = #next

O seletor de filhos inclui os filhos do elemento e seus relacionamentos com os elementos visíveis.

include cloud.*
// Same as
include cloud.backend
include cloud.ui

O seletor de descendentes inclui os descendentes do elemento SE eles tiverem um relacionamento com elementos visíveis.

include cloud.**
// Same as
include cloud.backend
include cloud.ui
include cloud.ui.webApp

O seletor de expansão inclui os filhos do elemento SE eles tiverem um relacionamento com elementos visíveis. Todos os outros filhos são omitidos.

include cloud._
// Same as
include cloud
include -> cloud.* ->

Predicados de relacionamentos incluem elementos apenas quando eles possuem relacionamentos que atendem às condições especificadas pelo predicado.

Inclua elementos quando eles tiverem relacionamentos direcionados (ou quando seus elementos aninhados tiverem):

// Include customer and cloud:
include customer -> cloud
// Include customer and nested elements of cloud (that have relationships):
include customer -> cloud.*

Inclua elementos quando eles tiverem qualquer relacionamento:

include customer <-> cloud

Inclua elementos quando eles tiverem relacionamentos de entrada vindos de elementos já visíveis.
Veja um exemplo baseado no modelo do exemplo com curinga:

incoming predicate.c4
view {
// visible element
include customer
// include nothing, customer has no relation to backend
include -> backend
// add ui,
// because customer has a relationship with nested ui.webApp
include -> ui
// add backend, because visible ui has a relationship to backend
// derived from ui.webApp -> backend.api
include -> backend
}
// This view includes customer and ui
view {
include
customer,
-> cloud.*
}

Inclua elementos somente quando eles tiverem relacionamentos de saída para elementos já visíveis:

include customer ->
include cloud.* ->

Inclua elementos aninhados de cloud que tenham qualquer relacionamento com elementos visíveis:

include -> cloud.* ->

Relacionamentos podem ser personalizados dentro da visão:

include
// Make lines red and solid
cloud.* <-> amazon.* with {
color red
line solid
},
// or only directed
customer -> cloud.* with {
// Override label
title 'Customer uses cloud'
navigateTo dynamicview1
},

Para personalizar a navegação a partir de um relacionamento:

include
webApp -> backend.api with {
navigateTo dashboardRequestFlow
}

O operador where restringe os resultados aplicando condições adicionais:

// include only microservices from nested
include cloud.*
where kind is microservice
// only microservices and not deprecated
include cloud.*
where
kind == microservice and // possible to use 'is' or '=='
tag != #deprecated // possible to use 'is not' or '!='
// Use logical operators
include cloud.*
where
not (kind is microservice or kind is webapp)
and tag is not #legacy
and (tag is #v1 or tag is #v2)

Predicados de relacionamentos

Quando where é usado com predicados de elementos, ele é aplicado aos elementos.
Quando é usado com predicados de relacionamentos, ele é aplicado aos relacionamentos.

include
// only relationships with tag #messaging
cloud.* <-> amazon.*
where tag is #messaging,
// only incoming http-requests
-> backend
where kind is http-request
-[http-request]-> backend
.http-request backend

Também é possível filtrar relacionamentos pela tag ou pelo tipo de seus endpoints.

include
// only relationships outgoing from elements with with tag #next
cloud.* -> amazon.*
where source.tag is #next,
// only incoming relations of elements with kind microservice
-> *
where target.kind is microservice

Junto com with

É possível usar where junto com with, mas where deve ser definido primeiro:

include *
where kind is microservice
with {
color amber
}

where também pode filtrar por valores de metadados de elementos ou relacionamentos:

// include only elements with environment="production"
include cloud.*
where metadata.environment is "production"
// exclude elements without a version metadata key
exclude *
where not metadata.version
// combine with other filters
include cloud.*
where
metadata.environment is "production"
and kind is not database

Valores booleanos de metadados podem ser comparados diretamente com true ou false (sem aspas):

// matches elements where critical is true
include *
where metadata.critical is true

Quando um valor de metadado é um array (por exemplo, regions ['us-east-1', 'eu-west-1']), is verifica se o array contém o valor:

// matches if "us-east-1" is one of the regions
include *
where metadata.regions is "us-east-1"

Para predicados de relacionamentos, filtre pelos próprios metadados do relacionamento ou pelos metadados de seus endpoints:

include
// only relationships with protocol="grpc"
cloud.* -> amazon.*
where metadata.protocol is "grpc",
// only relations from elements with env="production"
cloud.* -> *
where source.metadata.environment is "production",
// only relations to staging targets
* -> *
where target.metadata.environment is "staging"

Os mesmos filtros de metadados funcionam com predicados exclude:

// remove relationships with protocol="http"
exclude * -> *
where metadata.protocol is "http"
// remove relationships targeting staging elements
exclude * -> *
where target.metadata.environment is "staging"

Em visões de deployment, source.metadata.* e target.metadata.* seguem as regras de metadados de deployment. Os metadados definidos em uma instância implantada substituem os metadados do elemento correspondente no modelo.

Se você perceber que está repetindo os mesmos predicados em várias visões, pode defini-los como um grupo global:

global {
predicateGroup microservices {
include cloud.*
where kind is microservice
exclude *
where tag is #deprecated
}
}
views {
view of newServices {
include cloud.new.*
global predicate microservices
}
view of newBackendServices {
// Keep in mind that order is significant
global predicate microservices
include cloud.backend.*
}
}

É possível agrupar elementos, e isso é renderizado como um limite ao redor deles:

view {
group {
include backend
}
// with title
group 'Frontend' {
include frontend.*
}
// with style
group 'Service Bus' {
color amber
opacity 20%
border solid
include messageBroker.*
}
}

Grupos podem ser aninhados:

view {
group 'Third-parties' {
group 'Integrations' {
group 'Analytics' {}
group 'Marketing' {}
}
group 'Monitoring' {}
}
}

Grupos também podem referenciar grupos globais de predicados, permitindo reutilizar um conjunto de predicados e manter os elementos correspondentes dentro do grupo:

global {
predicateGroup microservices {
include cloud.*
where kind is microservice
}
}
views {
view of newServices {
include cloud.new.*
group 'Microservices' {
global predicate microservices
}
}
}

Predicados de estilo definem como os elementos são renderizados e são aplicados na ordem em que foram definidos, combinando-se com os anteriores:

view apiApp of internetBankingSystem.apiApplication {
include *
// apply to all elements
style * {
color muted
opacity 10%
}
// apply only to these elements
style singlePageApplication, mobileApp {
color secondary
size xlarge
}
// apply only to nested of apiApplication
style apiApplication.* {
color primary
multiple true
}
// apply to apiApplication and nested
style apiApplication._ {
color primary
}
// apply only to elements with specific tag
style element.tag = #deprecated {
color muted
}
// apply to elements not tagged
style element.tag != #deprecated {
opacity 20%
}
}

Estilos podem ser compartilhados dentro de um bloco views (“estilos locais”):

views {
// apply to all views in this block
style * {
color muted
opacity 10%
}
view of apiApp {
include *
style cloud.web.* {
color green
}
}
view of mobileApp {
include *
style cloud.ui.* {
color amber
}
}
}
views {
// Styles from previous block are not applied here
// ...
}

Estilos podem ser compartilhados globalmente.
Estilos globais devem ter um nome e ser definidos no bloco global:

global {
// Format:
// style <name> <targets> { ... }
style mute_all * {
color muted
opacity 10%
}
style applications
singlePageApplication._,
mobileApp._ {
color secondary
}
style mute_deprecated
element.tag = #deprecated {
color muted
}
}
views {
view of singlePageApplication {
// Styles are applied in the order they are defined
// 1. Apply global style
global style mute_all
// 2. Then this
style cloud.* {
color green
}
// 3. and 4.
global style applications
global style mute_deprecated
}
}

Estilos globais podem ser agrupados:

global {
// Define style group
styleGroup common_styles {
style singlePageApplication, mobileApp {
color secondary
}
style element.tag = #deprecated {
color muted
}
}
}
views {
view mobileApp of mobileApp {
include *
// Apply styles from group
global style common_styles
// Override
style mobileApp {
color primary
}
}
}
view {
include *
autoLayout LeftRight 120 110
}

Os parâmetros são:

  • direção: os valores possíveis são TopBottom (padrão), BottomTop, LeftRight, RightLeft.
  • distância entre ranks: opcional, deve ser um número positivo
  • distância entre nós: opcional, deve ser um número positivo

Visões podem ser estendidas para evitar duplicação, criar uma “baseline” ou, por exemplo, “slides” para uma apresentação:

views {
view view1 {
include *
}
view view2 extends view1 {
title 'Same as View1, but with more details'
style * {
color muted
}
include some.backend
}
// cascade inheritance
view view3 extends view2 {
title 'Same as View2, but with more details'
include * -> some.backend
}
}

Os predicados e as regras de estilo das visões estendidas são aplicados depois dos definidos nas visões ancestrais.

Uma visão estendida também herda o escopo:

views {
view view1 of cloud.backend {
title 'Backend components'
}
view view2 extends view1 {
include api // ✅ This is OK, references 'cloud.backend.api'
}
}

Você pode manter elementos específicos no mesmo nível horizontal/vertical (ou empurrá-los para o início/fim do layout) adicionando um bloco rank explícito à visão. Essas restrições de rank são encaminhadas ao mecanismo de layout Graphviz para produzir os efeitos de layout desejados.

view checkoutFlow {
include *
// keep the API nodes aligned
rank same {
cloud.backend.api,
cloud.backend.billingApi,
}
// make customers appear at the beginning of the diagram, exclusive of the elements
rank source {
customer
}
// render reporting systems at the end, exclusive of the elements
rank sink {
analytics,
dataWarehouse
}
}
  • Valores de rank permitidos: same, min, max, source, sink. Se omitido, same é assumido.
  • Os alvos são FqnRefs comuns, portanto você pode referenciar elementos aninhados como em outras regras. Alvos inexistentes ou duplicados são ignorados.
  • A restrição afeta apenas os elementos que realmente permanecem na visão calculada. Se um predicado remover posteriormente um elemento, ele também deixa de participar do bloco rank.
  • Regras de rank também participam do tiling manual de nós compostos, permitindo que restrições definidas pelo autor sejam combinadas com o layout automático em vez de entrar em conflito com ele.

Use restrições de rank com moderação — elas são mais úteis para ancorar colunas/linhas críticas (por exemplo, nós de entrada versus saída ou agrupamentos semânticos no lugar de containers) e obter um layout melhor.