Skip to content

Commit

Permalink
Merge branch 'latest' into docs/grid-empty-state
Browse files Browse the repository at this point in the history
  • Loading branch information
russelljtdyer committed Jul 12, 2024
2 parents c2e8d47 + 35537a9 commit dd98350
Show file tree
Hide file tree
Showing 84 changed files with 3,110 additions and 2,533 deletions.
4 changes: 4 additions & 0 deletions .github/styles/Vaadin/Abbr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
# ... with the exception of these:
exceptions:
- AJAX
- AKS
- API
- ARIA
- SAP
Expand All @@ -24,6 +25,7 @@ exceptions:
- DOM
- DPI
- DTO
- EKS
- EPSG
- EPUB
- FAQ
Expand Down Expand Up @@ -70,6 +72,7 @@ exceptions:
- SBOM
- SDK
- SPA
- SPI
- SQL
- SSH
- SSL
Expand All @@ -78,6 +81,7 @@ exceptions:
- TCP
- TEST
- TLS
- TSX
- REST
- UIDL
- URI
Expand Down
2 changes: 1 addition & 1 deletion .github/styles/Vaadin/ComplexWords.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ swap:
compensate: pay
#component: part
comprise: form|include
concept: idea
# concept: idea
concerning: about
confer: give|award
consequently: so
Expand Down
2 changes: 1 addition & 1 deletion .github/styles/Vaadin/ProductName.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ message: "Use '%s' instead of '%s'."
link: https://vaadin.com/docs/contributing/docs/styleguide#product-names
level: error
ignorecase: false
vocab: false
# swap maps tokens in form of bad: good
swap:
AppLayout: App Layout
Expand Down Expand Up @@ -52,7 +53,6 @@ swap:
Vaadin flow: Vaadin Flow
Vaadin fusion: Vaadin Fusion
Multi-Platform Runtime: Multiplatform Runtime
(?:[^\s]*) ?Vaadin [pP]latform: the Vaadin platform|The Vaadin platform
Vaadin Components: Vaadin components
Vaadin Framework: Vaadin (or "Vaadin 7 and/or 8" if needed)
Maven (?:Plugin|[pP]lug-in|[aA]dd-?on): Maven plugin
Expand Down
8 changes: 8 additions & 0 deletions .github/styles/Vaadin/VaadinPlatform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
extends: existence
message: Consider using "Vaadin" instead. Use "the Vaadin platform" only if absolutely needed.
level: warning
ignorecase: true
vocab: false
tokens:
- Vaadin Platform
- the Vaadin platform
7 changes: 7 additions & 0 deletions .github/styles/Vaadin/VaadinPlatformCase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extends: substitution
message: Don't capitalize "platform", and remember to include the definitive article ("the").
level: error
ignorecase: false
vocab: false
swap:
Vaadin Platform: the Vaadin platform|The Vaadin platform
1 change: 1 addition & 0 deletions .github/styles/Vaadin/Versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ extends: substitution
message: "Don't refer to a specific Vaadin version."
link: https://vaadin.com/docs/contributing/docs/styleguide#vaadin-versions
level: warning
vocab: false
swap:
'Vaadin (?:version )?[0-9]+(?:\.[0-9]+)?': Vaadin
1 change: 1 addition & 0 deletions .github/styles/config/vocabularies/Docs/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ JDK
MVC
PWA
RPC
SPI
SVG
UI
URI
Expand Down
4 changes: 2 additions & 2 deletions articles/_vaadin-version.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:vaadin-version: 24.4.3
:vaadin-flow-version: 24.4.2
:vaadin-version: 24.4.6
:vaadin-flow-version: 24.4.4
:vaadin-seven-version: 7.7.38
:vaadin-eight-version: 8.20.0
148 changes: 148 additions & 0 deletions articles/building-apps/architecture/components.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
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 of 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:

[[three-system-components]]
[.fill]
[link=images/three-components.png]
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, <<three-system-components, the previous component diagram>> 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, <<three-system-components, in the example>>, 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.

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

[.fill]
[link=images/components-with-spi.png]
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 a sub-package named `spi`. <<three-system-components, In the example>>, 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:

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

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 use the `@Import` annotation 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].
Loading

0 comments on commit dd98350

Please sign in to comment.