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

Editing Persistence - PR 3660 #3750

Draft
wants to merge 1 commit into
base: latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ description: How to manage your relational database schema with Flyway.
order: 30
---


= Flyway

Whenever you store data in a relational database, you have to manage the database schema in some way. When the application is first installed, you have to create the entire database schema. When new features are deployed, you have to update the database schema. You may need to add new tables to the database. You may need to add new columns to existing tables. You may need to move data from one table to another one, and delete the old one.
Expand All @@ -13,6 +14,7 @@ Some object-relational mapping tools, like Hibernate, can generate the initial s
[NOTE]
On this page, you'll learn enough about Flyway to get started using it in your Vaadin applications. Because Flyway has more features than presented here, you should also read the https://documentation.red-gate.com/flyway[Flyway Documentation].


== Migrations

Flyway is based on the concept of _migrations_. A migration is a script that performs some changes on your database. Every migration is versioned. As you implement new features, you add new migrations to the project. Flyway keeps track of which migrations have been applied to the database in a separate table. This table includes a checksum of every migration script.
Expand All @@ -23,6 +25,7 @@ Versioned migrations should not change after they have been applied. When Flyway

In addition to versioned migrations, Flyway also supports repeatable migrations. These migrations can change after they have been applied, and are automatically re-applied after every change. Repeatable migrations are always applied after the versioned migrations.


== Writing Migrations

You can write migrations in multiple languages, including Java, but the most common one is ordinary SQL. The migration scripts should follow a specific naming pattern. Versioned migrations start with an uppercase `V`, followed by a version number, two underscores `\__`, a description, and the suffix `.sql`. For example, a migration could be named `V1__initial_schema_setup.sql`.
Expand All @@ -33,6 +36,7 @@ You should store your SQL scripts in the `src/main/resources/db/migration` direc

For information about writing migrations in other languages than SQL, see the https://documentation.red-gate.com/flyway[Flyway Documentation].


== Migrating on Application Startup

Spring Boot has built-in support for Flyway. If the `org.flywaydb:flyway-core` module is on the classpath, Flyway is automatically executed on application startup.
Expand All @@ -56,12 +60,12 @@ Spring Boot declares the modules in its parent POM, so you don't have to look up
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <!--1-->
<artifactId>postgresql</artifactId> /* <1> */
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId> <!--2-->
<artifactId>flyway-database-postgresql</artifactId> /* <2> */
<scope>runtime</scope>
</dependency>
</dependencies>
Expand All @@ -77,6 +81,7 @@ To make Flyway use its own data source, set the `spring.flyway.[url,user,passwor

For more information, see the https://docs.spring.io/spring-boot/how-to/data-initialization.html#howto.data-initialization.migration-tool.flyway[Spring Boot Documentation].


== Migrating with Maven

Sometimes, you may want to run the Flyway migrations as a separate build step. For example, you may not want to make the DDL user credentials available to the application itself for security reasons. Flyway has a Maven plugin that allows you to run the migration scripts as a part of your build chain.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ description: How to use repositories to store and fetch data.
order: 10
---


= Repositories

The _repository_ was originally introduced as one of the building blocks of tactical Domain-Driven Design, but has since then become common in all business applications, mainly thanks to https://spring.io/projects/spring-data[Spring Data]. A repository is a persistent container of entities that attempts to abstract away the underlying data storage mechanism. At its minimum, it provides methods for basic CRUD operations: Creating, Retrieving, Updating, and Deleting entities.


== Collection Oriented

Collection oriented repositories try to mimic an in-memory collection, such as `Map` or `List`. Once an entity has been added to the repository, any changes made to it are automatically persisted until it has been deleted from the repository. In other words, there is no need for a `save` or `update` method.
Expand Down Expand Up @@ -54,6 +56,7 @@ repository.remove(CustomerId.of("XRxY2r9P"));

Collection oriented repositories can be quite difficult to implement. The repository implementation would have to know when an entity has been changed, so that it can write it to the underlying storage. Handling transactions and errors would also be non-trivial. This is a telling example of the underlying storage mechanism leaking into the repository abstraction.


== Persistence Oriented

Persistence oriented repositories do not try to hide the fact that the data has to be written to, and read from, some kind of external storage. They have separate methods for inserting, updating, and deleting the entity. If the repository is able to deduce whether any given entity has been persisted or not, the `insert` and `update` methods can be combined into a single `save` method. No changes to an entity are ever written to the storage without an explicit call to `save`.
Expand Down Expand Up @@ -107,6 +110,7 @@ Persistence oriented repositories are easier to implement than collection orient

*Unless you have a good reason for choosing collection-based repositories, you should use persistence oriented repositories in your Vaadin applications.*


== Query Methods

Although retrieving an entity by its ID is an important operation, it is not enough in most business applications. You need to be able to retrieve more than one entity at the same time, based on different criteria. If the dataset is big, you need to be able to split it into smaller pages and load them one at a time.
Expand Down Expand Up @@ -157,6 +161,7 @@ The challenge with this approach is that it is difficult, but not impossible, to

You can find examples of how to implement specification queries on the <<jpa#,JPA>> and <<jooq#,jOOQ>> documentation pages.


== Query Objects

Query specifications are useful when you are interested in fetching whole entities. However, you often need to write queries that only include a small part of the entity. For example, if you are building a customer list view that only shows the customers' names and email addresses, there is no point in fetching the complete Customer-entity. The repository now looks like this:
Expand Down Expand Up @@ -201,6 +206,7 @@ If you know the Command Query Responsibility Segregation (CQRS) architectural pa

// TODO Add link to using CQRS in Vaadin app, when that page has been written sometime in the future.


== Building

section_outline::[]
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
order: 40
---


= Pluggable Persistence

Most business applications only need a single persistence solution that remains for the lifetime of the application. In these cases, there is no point in hiding the persistence solution below a large abstraction layer. However, there are applications where you have to do this. For instance, some customers may want to use your application with a local database, while others want to use it with a remote web service.
Expand All @@ -21,13 +22,15 @@
[NOTE]
This page describes how to design entities and repositories as an SPI for other modules to implement. It assumes you have read the <<index#,Repositories>> documentation page.


== Entities

You have three options when it comes to designing entities for your persistence SPI: POJO:s, records, or interfaces.

=== POJO:s

POJO:s, or Plain Old Java Objects, are just that: ordinary Java objects. They may be mutable or immutable. They may contain business logic, or only act as data structures. They have to expose all the data that a repository needs to persist them. The easiest way of doing this is to expose the data through public getter methods, for example like this:
=== POJOs

Check failure on line 31 in articles/building-apps/application-layer/persistence/repositories/pluggable.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'POJOs'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'POJOs'?", "location": {"path": "articles/building-apps/application-layer/persistence/repositories/pluggable.adoc", "range": {"start": {"line": 31, "column": 5}}}, "severity": "ERROR"}

POJOs, or Plain Old Java Objects, are just that: ordinary Java objects. They may be mutable or immutable. They may contain business logic, or only act as data structures. They have to expose all the data that a repository needs to persist them. The easiest way of doing this is to expose the data through public getter methods, for example like this:

Check failure on line 33 in articles/building-apps/application-layer/persistence/repositories/pluggable.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'POJOs'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'POJOs'?", "location": {"path": "articles/building-apps/application-layer/persistence/repositories/pluggable.adoc", "range": {"start": {"line": 33, "column": 1}}}, "severity": "ERROR"}

Check warning on line 33 in articles/building-apps/application-layer/persistence/repositories/pluggable.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.JustSimply] Avoid using 'just'. It may be insensitive. Raw Output: {"message": "[Vaadin.JustSimply] Avoid using 'just'. It may be insensitive.", "location": {"path": "articles/building-apps/application-layer/persistence/repositories/pluggable.adoc", "range": {"start": {"line": 33, "column": 39}}}, "severity": "WARNING"}

[source,java]
----
Expand Down Expand Up @@ -130,6 +133,7 @@

// TODO Add links to validation.


=== Records

Java records are immutable, initialized through the constructor, and expose all their fields through public getter methods. This makes your SPI simpler, as there are less moving parts. It also makes your entities more like Data Transfer Objects (DTO), than entities. The `Project` POJO from the earlier example would look like this as a record:
Expand Down Expand Up @@ -169,6 +173,7 @@

// TODO Should write an article about how to do this.


=== Interfaces

If you want to give the repository implementation full control over your entities, you can define them as interfaces. For example, a `Product` entity interface could look like this:
Expand Down Expand Up @@ -253,6 +258,7 @@

The factory implementation would have to be a Spring managed bean, so that it can be injected into your services, or wherever it is needed.


==== Read-Only Entity Interfaces

If you declare entity interfaces that are read-only, leave out the `get` prefix from the getter methods. This makes them much easier to combine with Java records. Consider the following interface:
Expand All @@ -273,6 +279,7 @@
public record ProductRecord(Long productId, String name, String description) implements Product {}
----


== Repositories

The repositories are themselves a part of your SPI. Therefore, they are either interfaces, or abstract classes.
Expand Down
Loading