Skip to content

The Vaadin Way Architecture #3372

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

Merged
merged 23 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a9eeeef
First draft of "designing the architecture"
peholmst Apr 23, 2024
2e72f55
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 23, 2024
9d77684
Rename "designing" to "building blocks"
peholmst Apr 23, 2024
1f7aa14
Use C4 to introduce the architectural concepts
peholmst Apr 24, 2024
a1e80df
Tweaks to cross-references
peholmst Apr 24, 2024
2945e5d
Remove link to deep dive until it has been written.
peholmst Apr 25, 2024
e376a7e
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 25, 2024
54de9b2
First draft about monoliths
peholmst Apr 25, 2024
d3bfc62
First draft of microservices page.
peholmst Apr 26, 2024
0450b65
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 26, 2024
f249827
Edited most of documents added.
russelljtdyer Apr 26, 2024
8865ab3
More initial editing.
russelljtdyer Apr 27, 2024
90e931f
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 30, 2024
9b61ad4
Merge branch 'next' into vaadin-way-architecture
peholmst May 2, 2024
563a000
Merge branch 'next' into vaadin-way-architecture
peholmst May 2, 2024
80e2528
Merge branch 'next' into vaadin-way-architecture
peholmst Jun 5, 2024
a76442c
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 12, 2024
14e3c84
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 24, 2024
e73c03d
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 26, 2024
6f81752
Fix vale errors
peholmst Jun 26, 2024
53900c1
Add introduction
peholmst Jun 26, 2024
44e82b0
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 27, 2024
7de6d06
Move Building Apps down in the menu until it has more content
peholmst Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/styles/Vaadin/Abbr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ exceptions:
- SBOM
- SDK
- SPA
- SPI
- SQL
- SSH
- SSL
Expand Down
142 changes: 142 additions & 0 deletions articles/building-apps/architecture/components.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
title: System Components
description: Learn more about System Components and how to turn them into Java code.
order: 20
---


= System Components

The <<{articles}/building-apps/architecture/design#,Designing the Architecture>> page of the documentation discussed _system components_ and _UI components_. This page provides more about system components and how to turn them into Java code.

Vaadin applications are composed from system components, one of which is the user interface. The number of system components depends on the size and complexity of the application. A small application could have only one component. A large application might have ten or twenty components.

Here is an example of a Vaadin application with three components:

image:images/three-components.png[A diagram of three system components and a database]

In this example, the _Views_ component talks to the _Services_ component. The _Services_ component in turn talks to the _Entities_ component, which uses JPA to communicate with a relational database.


== Components in Java

The Java programming language has no component construct. Instead, you would use Java packages to model components. Thus, the component diagram above would correspond to the following Java package structure:

- `com.example.application` is a root package that contains the Spring Boot application class.
- `com.example.application.views` is a package corresponding to the _views_ system component.
- `com.example.application.services` is the package corresponding to the _services_ system component.
- `com.example.application.entities` is the package corresponding to the _entities_ system component.

For small system components, a package per component is enough. However, in more complex cases, you'll often need to create sub-packages to keep the code organized. You may also need to organize the system component packages themselves into parent packages.
// For more information about this, please see the <<{articles}/building-apps/project-structure#,Project Structure>> section of the documentation.


== Application Programming Interfaces

A system component can have an _Application Programming Interface_ (API) that allows other components to _call_ it. However, system components are not required to make themselves available to other components. For instance, a user interface system component is typically only called by the web browser and never by other system components. Therefore, it doesn't need an API at all.

In Java, the API consists of all _public_ classes, interfaces and methods inside the system component package. In other words, the simplest possible component with an API is a package that contains a single public Java class, that in turn contains a single public method.

All classes or methods that are not considered a part of the API should have a different visibility than public, such as package private.

A system component can inherit the API of another component on which it depends. For instance, in the example above, the services component can inherit the API of the entities component, like this:

[source,java]
----
package com.example.application.services;

import com.example.application.entities.OrderRepository;
import com.example.application.entities.DraftOrder;
import com.example.application.entities.CompletedOrder;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService { // <1>

private final OrderRepository repository;

OrderService(OrderRepository repository) { // <2>
this.repository = repository;
}

@Transactional
public CompletedOrder completeOrder(DraftOrder draftOrder) { // <3>
var completedOrder = draftOrder.complete();
return repository.save(completedOrder);
}
}
----
<1> `OrderService` is a part of the API, so it's _public_.
<2> The services system component handles instantiating the service itself. The constructor is _package private_, as it is not part of the API.
<3> `completeOrder` is a part of the API, so it's _public_. `DraftOrder` and `CompletedOrder` are inherited from the API of the entities system component.

As you can see, you're not required to use Java interfaces for the API unless you need or want to use them.


== Service Provider Interfaces

A system component can also have a _Service Provider Interface_ (SPI) that allows other components to plug into it. This is useful in cases where a component needs to interact with an external system, or when a component needs to externalize some business rules to another component.

An SPI is an interface that one component _declares_, and another component _implements_. In Java, it consists of at least one Java interface and optionally other types that the interface needs. For example, if the interface needs a Java class or a Java record as an input argument or return value, this would be a part of the SPI as well. An SPI is also allowed to include types from the system component's API.

In the example above, the services system component may need to integrate with an external system. Instead of putting all the code inside a single component, the services components declares an SPI. Then, a new system integration component is created that implements this SPI and handles the actual interaction with the external system:

image:images/components-with-spi.png[A diagram of four system components, an external system and a database]

This not only separates the concerns, but also protects the application from changes in the external system. If the external system's API changes, you only need to fix the system integration component. The rest of the system components can remain unchanged.

To distinguish between API and SPI classes and interfaces, you can put the SPI classes and interfaces inside their own sub-package called `spi`. In the example above, the SPI could look like this:

[source,java]
----
package com.example.application.services.spi; // <1>

import com.example.application.entities.CompletedOrder;

public interface ShippingSystem {

void shipCompletedOrder(CompletedOrder completedOrder); // <2>
}
----
<1> The interface is in the `spi` sub-package to make it clear that it's intended to be implemented by another system component.
<2> The `CompletedOrder` class, which is inherited from the API of the entities system component, can also be used by the SPI.

Sometimes, an interface can act as both the API and the SPI of the component at the same time. A typical example of this is the repository interface of a domain model component:

image:images/combined-spi-api.png[A diagram of three system components: Services, Domain Model and Persistence]

The repository interface is part of the API of the domain model and called by the services system component. However, the repository interface is also a part of the SPI of the domain model and implemented by the persistence system component. The persistence system component, in turn, talks to the database. In this case, using a sub-package `spi` is only confusing. Instead, JavaDocs should be used to explain the roles of the interface. Sometimes you have to be pragmatic.


== Instantiating Components

As Java has no component construct, a component instance consists of ordinary Java objects during runtime. These objects are instantiated by Spring, which also takes care of setting up the dependencies between them through dependency injection. Use _constructor injection_ into _final_ fields instead of autowiring into mutable fields, like this:

[source,java]
----
@Service
public class InvoiceGenerationService {

private final InvoiceRepository invoiceRepository;
private final AccountingSystem accountingSystem;
private final ApplicationEventPublisher eventPublisher;

InvoiceGenerationService(InvoiceRepository invoiceRepository,
AccountingSystem accountingSystem,
ApplicationEventPublisher eventPublisher) {
this.invoiceRepository = invoiceRepository;
this.accountingSystem = accountingSystem;
this.eventPublisher = eventPublisher;
}
}
----

Constructor injection has several benefits. First, it becomes clear what are the dependencies of the class. Second, it's impossible to instantiate the class without the necessary dependencies. Third, it's impossible to modify unintentionally the dependencies after instantiation. If the number of constructor arguments grows too large, the class has too many responsibilities and you should split it into smaller parts.

Usually, using Spring's component scanning and stereotype annotations (such as `@Component` or `@Service`) is enough to instantiate all the objects in your system component. However, if you need more fine grained control over the object creation, you can use Spring's Java-based container configuration. Inside your component, create a `@Configuration` annotated class and use `@Bean` methods to create the objects.

Unless you need to `@Import` the configuration class into some other configuration class, you can make it package private. This makes it clear that the configuration is not considered part of the system component's API.

If you're not familiar with Spring's Java-based container configuration, or you want to learn more about it, read the https://docs.spring.io/spring-framework/reference/core/beans/java/basic-concepts.html:[Spring Framework Documentation].
84 changes: 84 additions & 0 deletions articles/building-apps/architecture/design.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Design
page-title: Designing the Architecture
description: Explains how to use the C4 model to design architecture.
order: 10
---

// TODO Can you make the images zoomable by clicking on them?


= Designing the Architecture

Software architecture is a wide concept. No one thing is the software architecture. It has many aspects and can be looked at from various points of view. This page presents only one of them: the structure of the software.
// For other aspects, look at the <<{articles}/building-apps/architecture/deep-dive#,Deep Dive>> section of the documentation.

To explain a software architecture design to other developers, you need a good way of visualizing it. You've probably seen an architecture diagram. These aren't always helpful. Often times, they are an incoherent and inconsistent mess of boxes and arrows that confuses rather than clarifies. At the other end of the spectrum, you'll find dedicated modeling tools that use industry standard notations such as Unified Modeling Language (UML) or Systems Modeling Language (SysML). These are without doubt useful, but require you to learn a new notation and buy a dedicated modeling tool.

Fortunately, there is a middle way called the C4 model. The C4 is a model for visualising software architecture in a notation and tooling independent manner. The model is presented in detail at https://c4model.com:[c4model.com], but the main ideas are summarized on this page.

The C4 model takes its inspiration from digital maps, where you can zoom in and zoom out. Higher zoom levels offer better overview but have less details, whereas lower levels have more details while providing less overview. C4 allows you to create "maps" of your code and provides four zoom levels for you to use: Context, Containers, Components, and Code. You're not required to use all levels. It's fine to omit a level if you don't find it useful. There's no point in drawing diagrams for their own sake.


== Context

A _system context diagram_ is the highest level and a good starting point for new architecture. It depicts how the system you intend to build fits into the environment around it. It includes information about the users of the system, and any external system with which it integrates. It doesn't contain any technical information at all: that's reserved for the lower levels.

Here is an example system context diagram of a fictional appointment scheduling system:

image:images/c4-context.png[A system context diagram of a fictional appointment scheduling system]

You can see that the system has two different types of users: employees and customers. You can also see that the system integrates with two external systems: a Customer Resource Management (CRM) system for storing customer information, and an online mailing service for sending e-mails to customers.

The notation is simple: it consists of boxes, circles, lines, and text. It's important to remember to annotate the lines as well, as they describe the dynamics of the system. Observing the dynamics of a system can often tell you more than only looking at its static structure. For instance, if you left out the text from all the boxes in the diagram above, you could still understand what they mean from the annotated arrows.

You can add as much or as little text as you want, as long as it tells the story you want to tell. In the diagram above you could, for instance, make the users larger and include more detailed descriptions of why each user might want to use the system.

== Containers

If you were to zoom in on the software system in the context diagram, you'd get to the second level: a _container diagram_. In the C4 context, a container is either a deployable _application_ or a _data store_. It has nothing to do with Docker containers -- even though you may later package your applications as such. A container diagram shows the deployable parts of the system and how they interact with each other and any external systems.

Continuing with the fictional appointment scheduling system example, zooming in on the system box gives the following container diagram:

image:images/c4-container.png[A container diagram of a fictional appointment scheduling system]

As you can see, the diagram reveals a lot more details than the context diagram. You can see that the system consists of six containers: two web browsers (the employee's and the customer's), two Vaadin applications, a Hazelcast shared cache and a PostgreSQL database. You can see that the employee facing user interface is using Flow and the customer facing user interface is using Hilla and React. You can also see how the containers communicate with each other and with the external systems.

The notation follows the same pattern as the system context diagram: boxes, circles, lines, and text. You can add as much details as you need. For instance, if modeling the web browsers as separate containers is not relevant to you, you could simplify the diagram like this:

image:images/c4-container-simplified.png[A simplified container diagram of a fictional appointment scheduling system]

A Vaadin developer would be able to deduce from this diagram that the users are using their web browsers to interact with the applications.


== Components

If you were to zoom in on a container in the container diagram, you would get the third level: a _component diagram_. The term _component_ is used quite liberally in the software industry, often for different things depending on the context. In the C4 context, the term component is defined as an encapsulation of related functionality that has a well-defined interface and that can be instantiated.

However, as Vaadin also uses components to construct user interfaces, it makes sense to further the scope and type of a component with a prefix. Therefore, the components that you find in a component diagram are _system components_ and the components you find in a Vaadin user interface are _UI components_.

A component diagram shows how a container like a Vaadin application is constructed from system components. It includes information about the components' responsibilities, how they are implemented, and how they interact.

Continuing with the fictional appointment scheduling system example, zooming in on the scheduling application container gives the following component diagram:

image:images/c4-component.png[A component diagram of a scheduling application]

As you can see, the notation is again the same as in the past two levels.

If you had access to the source code of the scheduling application, this diagram would already be quite helpful in navigating it. You can see that there are six system components:

- The _Views_ system component contains the Flow user interface.
- The _Booking App Endpoints_ system component contains the https://grpc.io[gRPC] endpoints that the Booking App uses.

Check warning on line 71 in articles/building-apps/architecture/design.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Terms-App] Prefer 'application' over 'app'. Raw Output: {"message": "[Vaadin.Terms-App] Prefer 'application' over 'app'.", "location": {"path": "articles/building-apps/architecture/design.adoc", "range": {"start": {"line": 71, "column": 16}}}, "severity": "WARNING"}

Check warning on line 71 in articles/building-apps/architecture/design.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Terms-App] Prefer 'application' over 'app'. Raw Output: {"message": "[Vaadin.Terms-App] Prefer 'application' over 'app'.", "location": {"path": "articles/building-apps/architecture/design.adoc", "range": {"start": {"line": 71, "column": 110}}}, "severity": "WARNING"}
- The _Application Services_ system component contains the business logic of the entire application. It has an API that is called by both _Views_ and _Booking App Endpoints_.

Check warning on line 72 in articles/building-apps/architecture/design.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Terms-App] Prefer 'application' over 'app'. Raw Output: {"message": "[Vaadin.Terms-App] Prefer 'application' over 'app'.", "location": {"path": "articles/building-apps/architecture/design.adoc", "range": {"start": {"line": 72, "column": 160}}}, "severity": "WARNING"}
- The _Entities_ system component contains the entities and repositories and uses Spring Data and JPA. It has an API that's called by _Application Services_.
- The _CRM Integration_ and _Online Mailing Service Integration_ system components act as adapters between the scheduling application and the external CRM system and online mailing service, respectively. They both implement Service Provider Interfaces (SPI) that _Application Services_ has defined.

Because getting the system components right is an essential part of succeeding with building Vaadin applications, a separate <<{articles}/building-apps/architecture/components#,page>> has been devoted to that subject. There, you can learn more about API:s and SPI:s of system components and how to turn system components into Java code.

== Code

If you were to zoom in on a system component in the component diagram, you would get to the fourth and final level: a _code diagram_. This shows how a system component has been implemented in code and is typically drawn using UML or some other standard notation for code design. It's the most detailed of all the diagrams and, therefore, likely the one you'll use the least.

The code is the ultimate source of truth and it changes often, especially in the beginning of a project. This means that any code diagrams you draw are likely to become outdated at some point. It's therefore recommended to make code diagrams only for the most complex components. You can keep them up to date or discard them when they are no longer useful. The main point is to only draw the diagrams that help you tell the story and get the job done.

// TODO Add links to articles once they have been written
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions articles/building-apps/architecture/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Architecture
description: Learn how to architect business applications with Vaadin.
order: 10
---

Loading
Loading