Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Diagram: Architecture #5452

Merged
merged 72 commits into from
Aug 28, 2024

Conversation

NicolasNewman
Copy link
Collaborator

@NicolasNewman NicolasNewman commented Apr 10, 2024

📑 Summary

Architecture diagrams allows users to show relations between services

Resolves #5367

📏 Design Decisions

Parser

Terminology

  • Service: a node within a diagram. Represents services such as from AWS, Azure, Docker, etc
  • Group: a collection of related services (similar in presentation to subgraphs for other diagrams). This could be a VPC on AWS, different APIs which talk to one another, etc

Syntax

Currently, the syntax is defined as:

  • Services are defined as service {id}({icon})[{title}] (in {group_name})?
  • Groups are defined as group {id}[{title}] (in {group_id})?
  • Edges are defined as {id_1} ( ( )?(L|R|T|B)--(L|R|T|B)( ) )? {id_2}

Once I transition from jison to Langium, I plan to add the following extensions:

  • Groups: group {id}({icon})[{title}] (in {group_id})?
    • Allow icons to be used for group names
  • Edges: {id_1} ( ( )?(L|R|T|B)-([{label}])-(L|R|T|B)( ) )? {id_2}
    • Allow edges to have a label
    • Allow forked edges traversing the XY axis (see Layout for more info). I've yet to decide on the syntax for this
  • Services: service {id}( ({icon}) | ("{text}") )[{title}] (in {group_name})?
    • Allow text to be used for a service instead of an icon

Additionally, the syntax for groups (and potentially forked edges?) will be modified to better follow a declare then organize approach

Layout

  • The positions of services are determined by specifying a required direction for an edge between the two
  • Edges which traverse the X & Y axis (LT, RB, etc) are connected by two perpendicular lines
  • Each service can only have one edge per side
  • Internally, a spatial map of each service is maintained which specifies an (x,y) coordinate of where each service is in relation to one another ([0,1] is above [0,0]). This forces the layout to have a consistent grid like pattern

Comments

As mentioned in the issue, the LRTB syntax goes against the ethos of Mermaid. Additionally, while the spatial map creates very neatly organized layouts, it prevents users from having multiple edges going out of one side. Part of the reason I began designing it this way was to avoid this diagram type from essentially being a flowchart with icons.

Going forward, there's a few options that can fix this problem:

  1. Make the LRTB syntax optional. If they aren't ever specified, don't use the spatial map for generating the constraints for fcose and let it figure things out on its own.
  2. Keep the LRTB syntax required and instead add svg icon support to flowcharts. Ultimately the two diagram types are very similar in how they represent nodes and edges. My main reasoning for adding this new diagram type other then SVG support was for more precise control over node positioning like with block diagrams. I've already been using flowcharts to quickly mock up architecture diagrams before making something nicer in softwares like Lucidcharts.

No matter which option we choose, the LRTB syntax should be extended to:

  1. Allow forked edges. This means one edge out the X/Y axis would split into more edges that would expand outwards in the opposite axis. This would solve the issue of being limited to only a max of 4 edges per service.

SVG Icons

SVG icons are currently created and accessed through helper functions inside of rendering-util/svgRegister.ts. A global object stores the mapping of icon names to IconResolvers defined as

type IconResolver = (
  parent: Selection<SVGGElement, unknown, Element | null, unknown>,
  width?: number
) => Selection<SVGGElement, unknown, Element | null, unknown>;

IconResolvers can easily be created through the helper function createIcon. This function takes the SVG code as a string along with the original resolution. It returns a CB function taking the element to inject the SVG into and optionally a different size to scale it by.

const createIcon = (icon: string, originalSize: number): IconResolver => {
  return (
    parent: Selection<SVGGElement, unknown, Element | null, unknown>,
    size: number = originalSize
  ) => {
    parent.html(`<g style="transform: scale(${size / originalSize})">${icon}</g>`);
    return parent;
  };
};

A few generic icons I've created are located in rendering-utils/svg/. Additional icons can be added through the new iconLibraries config option. The types in svgRegister.ts are now exported from mermaid.ts, allowing developers to create their own icon library packages.

// Example icon

// rendering-util/svg/database.ts
export default createIcon(
  `<g>
      <rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
     ...
</g>`,
  80
);

// rendering-util/svg/index.ts
const defaultIconLibrary: IconLibrary = {
  database: database,
  // ...
};

// index.html
mermaid.initialize({
  theme: 'base',
  startOnLoad: true,
  // This is just an example. It is already loaded by default within MermaidAPI.ts
  iconLibraries: [defaultIconLibrary],
  // ...
});

Alternatives

From conversations in the linked issue, ZenUML already handles SVG icons on their end. Upon furthur inspection, they rely on declaring an svg module which essentially resolves the imported SVG as a string. Since the end result of both of these approaches are the same (having the SVG code as a string), personally I think it's debatable if we want to go through with this extra step. The main con I foresee is that it'll add more work for people who want to make their own icon library package as they will need to setup the declarations themselves.

On a side note, it looks like ZenUML directly added the official AWS icons to their repo here. IANAL but the terms Amazon states are:

Customers and partners are permitted by AWS to use the resources below to create architecture diagrams

This may mean we can make official icon packs in seperate packages, or have them be lazy loaded as needed. Ultimately I'll leave that decision up to you as that isn't my area of specialties.

Todo

  • Unit tests
  • Documentation
  • More generic default icons? If anyone has ideas let me know and I can put something together in Illustrator
  • Convert parser from jison to Langium
  • Implemented parser extensions
    • Edge labels
    • Icons as group names
    • Text instead of service icon
  • Forked edges
  • Edges between groups
  • More control over styling

📋 Tasks

Make sure you

Copy link

netlify bot commented Apr 10, 2024

Deploy Preview for mermaid-js failed.

Name Link
🔨 Latest commit 80c4b24
🔍 Latest deploy log https://app.netlify.com/sites/mermaid-js/deploys/66cf14bcfab24a00081e4861

Copy link

codecov bot commented Apr 10, 2024

Codecov Report

Attention: Patch coverage is 0.41588% with 2634 lines in your changes missing coverage. Please review.

Project coverage is 4.77%. Comparing base (0b9554c) to head (792a624).

Files Patch % Lines
.../src/diagrams/architecture/architectureRenderer.ts 0.00% 472 Missing ⚠️
...kages/mermaid/src/diagrams/architecture/svgDraw.ts 0.00% 366 Missing ⚠️
...aid/src/diagrams/architecture/architectureTypes.ts 0.00% 351 Missing ⚠️
...ermaid/src/diagrams/architecture/architectureDb.ts 0.00% 336 Missing ⚠️
...c/rendering-util/svg/digital-ocean/digitalOcean.ts 0.00% 321 Missing ⚠️
...es/mermaid/src/rendering-util/svg/aws/awsCommon.ts 0.00% 237 Missing ⚠️
...ackages/parser/src/language/architecture/module.ts 0.00% 79 Missing ⚠️
packages/mermaid/src/rendering-util/svgRegister.ts 0.00% 60 Missing ⚠️
...s/mermaid/src/rendering-util/svg/default/server.ts 0.00% 42 Missing ⚠️
...id/src/diagrams/architecture/architectureStyles.ts 0.00% 38 Missing ⚠️
... and 25 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #5452      +/-   ##
==========================================
- Coverage     5.32%   4.77%   -0.56%     
==========================================
  Files          317     342      +25     
  Lines        45097   50553    +5456     
  Branches       531     556      +25     
==========================================
+ Hits          2402    2413      +11     
- Misses       42695   48140    +5445     
Flag Coverage Δ
unit 4.77% <0.41%> (-0.56%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
...ages/mermaid/src/rendering-util/svg/aws/awsFull.ts 0.00% <ø> (ø)
packages/mermaid/src/themes/theme-default.js 94.52% <100.00%> (+0.15%) ⬆️
packages/parser/src/language/pie/valueConverter.ts 0.00% <ø> (ø)
.build/jsonSchema.ts 0.00% <0.00%> (ø)
packages/parser/src/language/architecture/index.ts 0.00% <0.00%> (ø)
packages/mermaid/src/docs/.vitepress/config.ts 0.00% <0.00%> (ø)
...s/mermaid/src/diagram-api/diagram-orchestration.ts 0.00% <0.00%> (ø)
packages/parser/src/language/index.ts 0.00% <0.00%> (ø)
packages/mermaid/src/mermaid.ts 0.00% <0.00%> (ø)
packages/mermaid/src/themes/theme-base.js 3.74% <0.00%> (-0.08%) ⬇️
... and 28 more

@NicolasNewman
Copy link
Collaborator Author

@sidharthv96 Continuing our conversation from the issue, I commented on SVG loading and my thought process behind how the layout is handled. Let me know what your thoughts are.

Additionally, an example of what forked edges would look like once implemented:
image

Regarding multiple edges going into one side of a service, it's possible but I'll need to spend some time figuring out the cleanest way to implement that functionality. Ultimately I was planning on using forked edges to handle that scenario but after looking at your example the arrows can't be as neatly represented the same way through forked edges.
image

@NicolasNewman
Copy link
Collaborator Author

As mentioned in the issue, the LRTB syntax goes against the ethos of Mermaid.

Looking at the examples it's not clear to me how to give the edges arrow ends? Can we do this similar to flow charts?

You can add ( / ) to the ends of the arrows to add them.

For example:

architecture
        service servC(server)[Server 1]
        service servL(server)[Server 2]
        service servR(server)[Server 3]
        service servT(server)[Server 4]
        service servB(server)[Server 5]

        servC (L--R) servL
        servC (R--L) servR
        servC (T--B) servT
        servC (B--T) servB

        servL (T--L) servT
        servL (B--L) servB
        servR (T--R) servT
        servR (B--R) servB

image

@NicolasNewman
Copy link
Collaborator Author

NicolasNewman commented Jul 22, 2024

I'll just note down points I find here, will do a full review soon.

architecture-beta should be used as the diagram keyword, this gives us a bit of wiggle room to tweak syntax till we finalize.

The bundle size has a 40% increase, which, I assume is mostly the icons? So we'd definitely want to move icons to a separate package. We can release it under the mermaid org itself. image

Issues with icon sizing image

How did you test the diagram to get that result? I can't seem to reproduce it.

@andig
Copy link

andig commented Jul 22, 2024

You can add ( / ) to the ends of the arrows to add them.

Not being a mermaid or architecture native, consistency would be a plus for me. Worth noting that the flowchart uses a different notation for array ends.

@knsv
Copy link
Collaborator

knsv commented Jul 23, 2024

Hi @NicolasNewman!

This looks really promising!

There are some ongoing projects that might be good for you to be aware of, as they touch on what you are doing here:

The first one is: Refactoring Rendering in Mermaid (#5237):
• We are separating layout from rendering.
• It simplifies adding new layout algorithms, as most rendering details remain unaffected—only the sizing of nodes is layout-specific.
• It will also ease the introduction of new diagrams. You simply set up data in the Data4Layout structure, add necessary node types, and hand it over to rendering. Reusing shapes from existing diagrams becomes trivial. New layout algorithms can be added as needed; we support ELK and Dagre from the start.
• It also adds native support for Rough.js/hand-drawn look.

The initial PR will convert flowcharts and state diagrams, with plans to extend this to all graph-based diagrams.

Note: This shouldn’t block your initial PR, but it would be great to collaborate on converting to the new structure eventually, allowing for multiple layout options.

The second activity is that we are adding a new Syntax for Flowchart attributes (Another PR):
• This will simplify adding attributes to nodes, enabling new flowchart shapes without creating new shorthand for each.
• It introduces a shape attribute for more exotic shapes, avoiding the limited combinations of ({[]})/.
• We are also considering an icon attribute, which aligns well with your icon functionality. I agree on the value of lazy-loaded icon packs to manage the potentially large number of icons. One should probably able to add external icon packs as well.

Looking forward to the architecture diagrams as well as the icons.

@knsv
Copy link
Collaborator

knsv commented Jul 23, 2024

Another note on the syntax:

architecture
        group left
        group right
        service left_disk(disk)[Disk] in left
        service top_disk(disk)[Disk] in left
        service bottom_disk(disk)[Disk] in left
        service top_gateway(internet)[Gateway] in right
        service bottom_gateway(internet)[Gateway] in right
        junction juncC in left
        junction juncR in right

        left_disk R--L juncC
        top_disk B--T juncC
        bottom_disk T--B juncC


        top_gateway (B--T juncR
        bottom_gateway (T--B juncR

        juncC{group} R--L) juncR{group}

In the defence of consistency. What if the port could be attached to the node for instance with a colon then you could use regular mermaid arrow syntax to simplify for users. Then this:

 juncC{group} R--L) juncR{group}

would turn into:

juncC{group}:R --> juncR{group}:L

or even like this depending on the grammar

juncC{group}:R --> L:juncR{group}

(I suspect the grammar with the port at the end is easier to handle)

@NicolasNewman
Copy link
Collaborator Author

@knsv

I'm not particularly attached to the current syntax. I actually like the colon version more in the 2nd example you gave.

I'll take a look at the first PR you linked, it sounds like an exciting change! I'd be happy to collaborate on that in the future.

With regards to icons, I added a page to the documentation in this PR regarding how to use icons and create external icon packs. Let me know your thoughts once you get the chance to look at it. It's designed to where it should easily be easy to integrate with other diagrams but there may be some factors I haven't considered.

@wgordon17
Copy link

Just out of curiosity...how close is this feature to becoming reality?

@knsv
Copy link
Collaborator

knsv commented Aug 13, 2024

We are getting close to getting this in. Rough guess 1-2 weeks.

@ashishjain0512
Copy link
Collaborator

@NicolasNewman Awesome work. @knsv and I went through the demos and thought this diagram type be a great addition.

We think that using the Icon packs that are lazily loaded makes sense for the Icon registering and loading, but the ZenUML approach to creating the Icon pack will be easier to maintain. The getIcon() internal API in svgDraw can stay the same.

We should make several Icon packs like AWS, Google, mermaid, etc, in the repo, and lazy load them in Mermaid initialize.
This will help other members of the mermaid open-source community to create different icon packs, and users can add and use the ones they want.

@knsv
Copy link
Collaborator

knsv commented Aug 14, 2024

Once the conflicts are resolved, we will merge this PR.

@NicolasNewman
Copy link
Collaborator Author

NicolasNewman commented Aug 14, 2024

Conflicts are now resolved.

@ashishjain0512 Do you want me to convert the other icon packs currently in this PR to be lazy loaded as well? Currently only aws:full is.

I'm assuming the structural changes will be made afterwards? One improvement to the documentation of this feature I haven't touched on yet was adding the names and a preview of the icons available. I'm not as familiar with the auto-generation portion of the documentation but I'd think another benefit of using the ZenUML approach is the icons being in an SVG file will be easier to add to the docs then if they were in a TS file.

@knsv
Copy link
Collaborator

knsv commented Aug 19, 2024

@NicolasNewman For the packs: Yes, please do!

We also appreciate the work you have been doing on the project. As such, we would be happy for you to join the core team! If you are interested, ping me directly in Discord.

Copy link

changeset-bot bot commented Aug 28, 2024

🦋 Changeset detected

Latest commit: 80c4b24

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal: Cloud Architecture Diagram
7 participants