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

My second application tutorial #34401

Merged
merged 1 commit into from
Jul 25, 2023
Merged
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
5 changes: 5 additions & 0 deletions docs/src/main/asciidoc/_includes/devtools/create-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,8 @@ ifndef::devtools-no-gradle[]
To create a Gradle project, add the `-DbuildTool=gradle` or `-DbuildTool=gradle-kotlin-dsl` option.
endif::[]
****

For Windows users:

- If using cmd, (don't use backward slash `\` and put everything on the same line)
- If using Powershell, wrap `-D` parameters in double quotes e.g. `"-DprojectArtifactId={create-app-artifact-id}"`
342 changes: 342 additions & 0 deletions docs/src/main/asciidoc/getting-started-dev-services.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
////
This document is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////

[id="getting-started-dev-services-tutorial"]
= Your second Quarkus application
include::_attributes.adoc[]
:diataxis-type: tutorial
:categories: getting-started, data, core

holly-cummins marked this conversation as resolved.
Show resolved Hide resolved

This tutorial shows you how to create an application which writes to and reads from a database.

Check warning on line 14 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 14, "column": 53}}}, "severity": "INFO"}
You will use Dev Services, so you will not actually download, configure, or even start the database yourself.
You will also use Panache, a layer on top of Hibernate ORM, to make reading and writing data easier.


== Prerequisites

:prerequisites-time: 30 minutes
:prerequisites-docker:
:prerequisites-no-graalvm:
include::{includes}/prerequisites.adoc[]

This tutorial builds on what you learned writing xref:{doc-guides}/getting-started.adoc[your first Quarkus application].
You will not need the code from that application, but make sure you understand the concepts.

Check warning on line 27 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses it. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses it.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 27, "column": 55}}}, "severity": "WARNING"}

== Solution

We recommend that you follow the instructions from <<Bootstrapping the project>> onwards to create the application step by step.

Check warning on line 31 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 31, "column": 4}}}, "severity": "INFO"}

Check warning on line 31 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'onwards'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'onwards'?", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 31, "column": 82}}}, "severity": "WARNING"}

However, you can go right to the completed example.

Download an {quickstarts-archive-url}[archive] or clone the git repository:

Check warning on line 35 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.CaseSensitiveTerms] Use 'Git' rather than 'git'. Raw Output: {"message": "[Quarkus.CaseSensitiveTerms] Use 'Git' rather than 'git'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 35, "column": 61}}}, "severity": "INFO"}

[source,bash,subs=attributes+]
----
git clone {quickstarts-clone-url}
----

The solution is located in the `getting-started-dev-services` {quickstarts-tree-url}/getting-started-dev-services[directory].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 'dev-service', shouldn't it be 'crud' ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or 'rest'

Copy link
Contributor Author

@holly-cummins holly-cummins Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it could be 'rest', because https://github.com/quarkusio/quarkus-quickstarts/tree/main/getting-started is a rest application. It could be 'crud' ... to me, dev services was the big gap that we didn't have content for, so I named it that way. But you're right that crud is the characteristic feature of the application code, and we do already have https://github.com/quarkusio/quarkus-quickstarts/tree/main/getting-started-reactive-crud/. So I'll rename it that way to be consistent.

There's also overlap between the app and https://github.com/quarkusio/quarkus-quickstarts/tree/main/hibernate-orm-panache-quickstart, but the 'crud' name doesn't make the overlap worse, just more obvious. :) The key difference is that one is designed to be coded by a user and the other is intended to be downloaded on app creation, so I think it's right that they stay separate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think I've changed my mind again. getting-started-dev-services seems like the right file name for this article, and then people would expect that the code in the other repo would have the same name as the article.


:sectnums:
:sectnumlevels: 3
== Outline steps

- Bootstrap the application
- Update the application to read user input
- Create a Panache Entity
- Read and write the entity
- Configure an external database using a profile

== Setting up an interactive application

=== Bootstrapping the project

Check warning on line 56 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 56, "column": 1}}}, "severity": "INFO"}

Check warning on line 56 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '2.1. Bootstrapping the project'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '2.1. Bootstrapping the project'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 56, "column": 1}}}, "severity": "INFO"}

The easiest way to create a new Quarkus project is to open a terminal and run the following command:

:create-app-artifact-id: getting-started-dev-services
:create-app-extensions: resteasy-reactive
:create-app-code:
include::{includes}/devtools/create-app.adoc[]

For an explanation of what's in the generated application, see the xref:getting-started.adoc[First application guide].

=== Running the application

Check warning on line 67 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 67, "column": 1}}}, "severity": "INFO"}

Check warning on line 67 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '2.2. Running the application'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '2.2. Running the application'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 67, "column": 1}}}, "severity": "INFO"}

Launch the application in dev mode

Check warning on line 69 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'start' or 'open' rather than 'Launch' unless updating existing content that uses it. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'start' or 'open' rather than 'Launch' unless updating existing content that uses it.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 69, "column": 1}}}, "severity": "WARNING"}

include::{includes}/devtools/dev.adoc[]

Once the application is up, visit http://localhost:8080/hello. It should show a "Hello from RESTEasy Reactive" message.

=== Accepting user input

Let's make the application a bit more interactive.
Open the project in your IDE and navigate to `src/main/java/org/acme/GreetingResource.java'
Add a query param in the `hello` method.
(The `org.jboss.resteasy.reactive.RestQuery` annotation is like the Jakarta REST `@QueryParam`
annotation, except you don't need to duplicate the parameter name.)

[source, java]
----
holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
public String hello(@RestQuery String name) {
return "Hello " + name;
}
----

Visit http://localhost:8080/hello?name=Bloom.

You should see a personalised message: `Hello Bloom`.

=== Fixing the tests

In your Quarkus terminal, type 'r' to run the tests. You should see
that your application changes broke the tests!

To fix the tests, open `src/test/java/org/acme/GreetingResourceTest.java`
and replace

[source, java]
----
.body(is("Hello from RESTEasy Reactive"));
----

with

[source, java]
----
.body(containsString("Hello"));
----

This still validates the HTTP endpoint, but it's more flexible
about the expected output.
You should see in your terminal that the tests are now passing.

== Adding persistence

=== Creating a Panache Entity

1. To add the persistence libraries, run

:add-extension-extensions: hibernate-orm-panache jdbc-postgresql
include::{includes}/devtools/extension-add.adoc[]

The application will record the names of people it greets. Define an Entity
by creating a `Greeting.java` class. Add the following content:

[source, java]
----
holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;

@Entity
public class Greeting extends PanacheEntity {
public String name;
}
----

The entity makes use of xref:{doc-guides}hibernate-orm-panache.adoc[Panache], a layer on top of Hibernate ORM.
Extending `PanacheEntity` brings in a range of methods for reading, writing, and finding data.
Because all the data access methods are on the `Greeting` entity, rather than on a separate data access class,
this is an example of the active record pattern.

The `Greeting` table will have one column, a field called `name`.

=== Writing data

To use the new entity, update the `hello` method to start writing some data.

Change the method to the following:

[source, java]
----
holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
@GET
@Transactional
@Produces(MediaType.TEXT_PLAIN)
public String hello(@QueryParam("name") String name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly annoying...

As GET must be idempotent, and so should not change the server state.
I would recommend using @post (or having an @post endpoint).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knooow, but if we use post, it's (a) harder to test and (b) less realistic. No one would expect to do a post to retrieve information, if the information under discussion isn't changed by the request. Many pages store analytics about site traffic to their @get endpoints. Since something somewhere changed, arguably the GET endpoint is changing state. No website ever has said 'we use Google Analytics, so you must use POST to visit our pages.' :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's a question of the semantics of what the server state is. It's sort of hard to reason about semantics for an endpoint that just says "hello," because the whole premise doesn't actually have value. But I would argue the 'intended effect' is to say hello, in an idempotent way. The persistence of the name is a traffic-monitoring side effect.

Greeting greeting = new Greeting();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a POST, you can receive the Greeting directly from the body, and just do .persist()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading a Greeting from the body doesn't feel right. I don't think the user is saying hello to us and sending us a greeting, I think they're asking us to say hello to them. That's more consistent with the original function of the application that we are adapting.

Of course, greetings are peskily symmetrical, so we may never be able to decide which it is.

Would it help if we called the thing we persisted something like else, so it was obvious it would be inappropriate for it to be passed in? Like InteractionLog or something?

greeting.name = name;
greeting.persist();
return "Hello " + name;
}
----

Don't forget the `@Transactional` annotation, which ensures writes are wrapped
in a transaction.

[NOTE]
.GETs should not change application state.
Generally, you shouldn't do state updates in a `GET` REST method, but here it makes
trying things out simpler. Let's assume what's being written is a logging "side effect",
rather than a meaningful state changes!
holly-cummins marked this conversation as resolved.
Show resolved Hide resolved

Try out the updated endpoint by visiting http://localhost:8080/hello?name=Bloom.
You should see a "Hello Bloom" message.

=== Reading data
Although the new persistence code seems to be working without errors, how
do you know anything is being written to the database?

Add a second REST method to `GreetingResource`.

holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
[source, java]
----
@GET
@Path("names")
@Produces(MediaType.TEXT_PLAIN)
public String names() {
List<Greeting> greetings = Greeting.listAll();
String names = greetings.stream().map(g-> g.name)
.collect(Collectors.joining (", "));
return "I've said hello to " + names;
}
----

To try it out, visit http://localhost:8080/hello?name=Bloom, and then http://localhost/hello/names.

You should see the following message: "I've said hello to Bloom".

[IMPORTANT]
holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
.a container runtime is required.
====
Don't forget that you need to have a container runtime available, or
you will start seeing failures in the Quarkus logs at this point.
====

== Dev services

Reading and writing to the database seems to be working well, but that's a bit unexpected.
Where did a PostgreSQL database come from? You didn't set anything up.

The database is being managed using xref:{docfile}/dev-services.adoc[Dev Services].
Dev Services take care of stopping and starting services needed by your application.
Because you
included the `jdbc-postgresql` dependency, the database is a containerised PostgreSQL database.
If you'd added `jdbc-mysql` insead, you would have gotten a containerised MySQL database.

holly-cummins marked this conversation as resolved.
Show resolved Hide resolved
If you like, use your container tool to see what containers are running.
For example, if you're using Docker, run `docker ps`, and for podman, run `podman ps`.
You should see something like the following:

----
ff88dcedd899 docker.io/library/postgres:14 postgres -c fsync... 20 minutes ago Up 20 minutes 0.0.0.0:34789->5432/tcp nostalgic_bassi
----

Stop Quarkus and run `docker ps` again.
You should see nothing running (it may take a few moments for containers to shut down).
Quarkus will automatically stop the container when your application stops.

=== Initialising services

If you play with your code some more, you may notice that sometimes, after making an application change, http://localhost/hello/names doesn't list any names.

Check failure on line 234 in docs/src/main/asciidoc/getting-started-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsErrors] Use 'you' rather than 'I'. Raw Output: {"message": "[Quarkus.TermsErrors] Use 'you' rather than 'I'.", "location": {"path": "docs/src/main/asciidoc/getting-started-dev-services.adoc", "range": {"start": {"line": 234, "column": 1}}}, "severity": "ERROR"}
What's going on? By default, in dev mode, with a Dev Services database,
Quarkus configures Hibernate ORM database generation to be `drop-and-create`.
See the xref:{docfile}/hibernate-orm.adoc#quarkus-hibernate-orm_quarkus.hibernate-orm.database-database-related-configuration[Hibernate configuration reference] for more details.
If a code change triggers an application restart, the database tables
will be dropped (deleted) and then re-created.

This is convenient, but what if you'd prefer the database to always have content?
That would make testing easier.
If you provide an `import.sql` file, Quarkus will use that to initialise
the database on each start.

1. Make a `src/main/resources/import.sql` file in your project
2. Add the following SQL statements:

[source, sql]
----
INSERT INTO Greeting(id, name)
VALUES (nextval('Greeting_SEQ'), 'Alice');
INSERT INTO Greeting(id, name)
VALUES (nextval('Greeting_SEQ'), 'Bob');
----

Now, hit `s` in your dev mode session, to force a full restart. Then visit http://localhost:8080/hello/names.

You'll see that Alice and Bob are always included in the list of names.

== Controlling Dev Services

=== Using an external database instead

What if you'd rather use an external database that you manage yourself?
Add the following to `src/main/resources/application.properties`:

[source, properties]
----
# configure your datasource
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = leopold
quarkus.datasource.password = bloom
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/mydatabase
----

This tells Quarkus that you don't want it to start a Dev Service,
because you have your own database. You don't need to worry about starting
the database, because you're just seeing how to change the configuration.

Visit `http://localhost:8080/hello/names`. Instead of a list of names,
you'll get a red error screen. In the terminal where Quarkus is running.
you'll see the following stack error message:

----
2023-06-28 19:18:22,880 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /hello?name=fred failed, error id: 4f9b5ce6-3b08-41c5-af36-24eee4d1dd2b-2: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.] [n/a]
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:98)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56)
...
----

This makes sense; you've disabled the database Dev Service, but you haven't
started your own database.

=== Using profiles

Unless you want to, don't worry about setting up an external database
to resolve the connection error. Instead, you will go back to using the Dev Service.
It made life easy!

But what about production? You won't want to use Dev Services in production.
In fact, Quarkus only starts Dev Services in dev and test modes.

Wouldn't it be nice to configure an external database,
but have it *only* used in production, so you could still use dev services the rest of the time?

Add a `%prod.`
prefix to the database configuration. This means the configuration
only applies to the xref:{docfile}/config-reference#profiles[prod profile]

The configuration should look like this:

[source, properties]
----
# configure your datasource
%prod.quarkus.datasource.db-kind = postgresql
%prod.quarkus.datasource.username = leopold
%prod.quarkus.datasource.password = bloom
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/mydatabase
----

Now the external database will be used in prod mode,
and Dev Services will be used in dev and test modes.

Check http://localhost:8080/hello/names. It should be working again,
because the Dev Services have been re-enabled.
Notice that there was no need to restart Quarkus for any of these changes.


:sectnums!:
== Summary

You've taken a simple REST application and updated it to write and read
data from a database, using Hibernate ORM and Panache. The data was persisted to
a 'real' database, without you having to configure anything.


== References

* xref:{doc-guides}dev-services.adoc[Dev Services]

* xref:{doc-guides}hibernate-orm-panache.adoc[Hibernate ORM with Panache]
7 changes: 0 additions & 7 deletions docs/src/main/asciidoc/getting-started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,11 @@ The solution is located in the `getting-started` link:{quickstarts-tree-url}/get

The easiest way to create a new Quarkus project is to open a terminal and run the following command:

For Linux & MacOS users

:create-app-artifact-id: getting-started
:create-app-extensions: resteasy-reactive
:create-app-code:
include::{includes}/devtools/create-app.adoc[]

For Windows users:

- If using cmd , (don't use backward slash `\` and put everything on the same line)
- If using Powershell , wrap `-D` parameters in double quotes e.g. `"-DprojectArtifactId=getting-started"`

It generates the following in `./getting-started`:

* the Maven structure
Expand Down
Loading