Skip to content

Commit

Permalink
say jex
Browse files Browse the repository at this point in the history
  • Loading branch information
SentryMan committed Jan 19, 2025
1 parent 5d395a7 commit f10b88e
Show file tree
Hide file tree
Showing 49 changed files with 2,212 additions and 2,717 deletions.
18 changes: 1 addition & 17 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/modules.xml
Expand All @@ -21,21 +18,8 @@ target/
.springBeans
.sts4-cache

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

data
.env
data
1 change: 1 addition & 0 deletions .mvn/jvm.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-XX:+EnableDynamicAgentLoading
Binary file removed .mvn/wrapper/maven-wrapper.jar
Binary file not shown.
18 changes: 0 additions & 18 deletions .mvn/wrapper/maven-wrapper.properties

This file was deleted.

41 changes: 34 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
FROM maven:3.9.9-amazoncorretto-23 AS build
FROM amazoncorretto:23 AS builder

COPY src src
COPY pom.xml pom.xml
RUN dnf install -y binutils

RUN mvn clean package assembly:single
RUN jlink \
#when it comes to jlink, one bad apple spoils the bunch.
#if postgres was modular, then I could have let jlink take care of finding the JDK modules
#but since it's not, I have to determine and specify all the modules I need
--add-modules java.base,java.instrument,java.logging,java.management,java.naming,java.net.http,java.sql,jdk.httpserver \
--strip-debug \
--no-header-files \
--no-man-pages \
--output /jre

FROM amazoncorretto:23
# If all my dependencies were modular, I would have done this
# COPY /target/modules /modules
# RUN jlink -p /modules \
# --add-modules avaje.realworld \
# --strip-debug \
# --no-header-files \
# --no-man-pages \
# --output /jre

COPY --from=build target/server-distribution ./server-distribution
FROM gcr.io/distroless/cc-debian12

ENTRYPOINT ["sh", "server-distribution/bin/server.sh"]
COPY --from=builder /jre /jre
# COPY native lib for java
COPY --from=builder /usr/lib64/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1
# for ARM
# COPY --from=builder /usr/lib64/libz.so.1 /lib/aarch64-linux-gnu/libz.so.1

COPY /target/modules /modules

ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70.0 -Duser.timezone=\"America/New_York\""

ENTRYPOINT ["/jre/bin/java","-p", "/modules", "-m", "avaje.realworld"]

# If all my dependencies were modular, I would have done this
# ENTRYPOINT ["/jre/bin/java", "-m", "avaje.realworld"]
53 changes: 17 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,38 @@
# ![RealWorld Example App](logo.png)

> ### JDK HTTP Server codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.
> ### Avaje + Ebean + JDK HTTP Server codebase containing real world examples (CRUD, JWT auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.
This codebase was created to demonstrate a fully fledged fullstack application built with the JDK HTTP Server including CRUD operations, authentication, routing, pagination, and more.
This codebase was created to demonstrate a lightweight fully modular backend application built with the JDK HTTP Server including CRUD operations, authentication, routing, pagination, and more.

For more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo.

This is deployed [here](https://jdk-httpserver-realworld.onrender.com/)
For more information on how this works with other frontends/backends, head to the [RealWorld](https://github.com/gothinkster/realworld) repo.

# How it works

This is built up of a few components. Primarily

* The [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/22/docs/api/jdk.httpserver/module-summary.html) module which provides the API that is programmed against
* [Jetty](https://github.com/jetty/jetty.project) which provides the actual backing implementation for `jdk.httpserver`
* The [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.httpserver/module-summary.html) module as the HTTP Server
* [Ebean ORM](https://ebean.io) as the ORM
* [Postgresql](https://postgresql.org) for the database
* [RainbowGum](https://github.com/jstachio/rainbowgum) for logging

Then, serving specific tasks:

* [dev.mccue.jdk.httpserver](https://github.com/bowbahdoe/jdk-httpserver) for providing a `Body` abstraction
* [dev.mccue.jdk.httpserver.regexrouter](https://github.com/bowbahdoe/jdk-httpserver-regexrouter) for basic request routing
* [dev.mccue.json](https://github.com/bowbahdoe/json) for reading and writing JSON
* [dev.mccue.jdk.httpserver.json](https://github.com/bowbahdoe/jdk-httpserver-json) for using JSON as a `Body` and reading it from `HttpExchange`s
* [dev.mccue.urlparameters](https://github.com/bowbahdoe/urlparameters) for parsing query params
* [dev.mccue.jdbc](https://github.com/bowbahdoe/jdbc) for `UncheckedSQLException` and `SQLFragment`
* [io.github.cdimascio.dotenv.java](https://github.com/cdimascio/dotenv-java) for local development `.env` files
* [Avaje Jex](https://avaje.io/jex) for providing routing abstractions over the `jdk.httpserver`.
* [Avaje Jsonb](https://avaje.io/jsonb) for reading and writing JSON
* [Avaje HTTP Server](https://avaje.io/http-server) for generating routing code for Jex from Jax-RS style controllers
* [Avaje Inject](https://avaje.io/inject) for Dependency Injection
* [Avaje Config](https://avaje.io/config) for reading configuration files
* [Avaje Validation](https://avaje.io/validator) for bean validation
* [java-jwt](https://github.com/auth0/java-jwt) for JWT token validation
* [slugify](https://github.com/slugify/slugify) for turning text into a url sage slug
* [com.zaxxer.hikari](https://github.com/brettwooldridge/HikariCP) for connection pooling
* [bcrypt](https://github.com/patrickfav/bcrypt) for password salt and hashing
* [org.slf4j](https://github.com/qos-ch/slf4j) as a logging facade

Almost all the code is contained in the [`RealWorldAPI`](https://github.com/bowbahdoe/jdk-httpserver-realworld/blob/main/src/main/java/dev/mccue/jdk/httpserver/realworld/RealWorldAPI.java) class. If any of the choices made here offend your sensibilities
I encourage forking and showing the way you would prefer it be done. If you think something is done in a subpar way or
is otherwise objectively broken please open an issue.

Specifically, I would encourage folks to try and

* Split up the `RealWorldAPI` class. Where are the natural boundaries?
* Try using their database abstraction of choice. What would this look like with `Hibernate`, `JOOQ`, or `JDBI`? Would there be fewer or more round trips to the database?
* Try using their JSON library of choice.
* Try to do the whole persistence/service/etc. split. Does that make things better?
* Add unit tests. For this exact thing there are already API tests I was able to just use, but how would testing look with JUnit?
* etc.

I personally see a lot of areas for improvement once string templates are real. Counting `?`s in big queries is maybe the biggest
remaining "raw" JDBC shortcoming.

# Getting started

## Prerequisites

* Java 22 or above
* Java 21 or above
* SDKMan
* Docker

Expand Down Expand Up @@ -80,13 +61,13 @@ $ cd ..
Then to run the server either

* open the project in your editor
* run it through maven (`./mvnw exec:java -Dexec.mainClass="dev.mccue.jdk.httpserver.realworld.Main"`)
* run it through terminal (`./run.sh"`)
* run it through docker

```
$ docker build -t realworld .
$ docker run realworld
$ docker build -t real .
$ docker run real
```

The `.env` file for this project is committed to the repo. Note that in general this is a bad idea/practice, but the
only secrets here are for the local database connection so it's fine.
only secrets here are for the local database connection so it's fine.
67 changes: 25 additions & 42 deletions migrations/scripts/20241126081352_first_migration.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
-- // First migration.
CREATE EXTENSION citext;
CREATE EXTENSION pgcrypto;

CREATE SCHEMA realworld;


CREATE FUNCTION realworld.set_current_timestamp_updated_at()
RETURNS TRIGGER AS $$
DECLARE
Expand All @@ -19,7 +21,7 @@ CREATE TABLE realworld.user (
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
username text not null unique,
password_hash text not null,
password_hash bytea not null,
email citext not null unique,
bio text not null default '',
image text,
Expand All @@ -36,13 +38,12 @@ CREATE TABLE realworld.article (
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
user_id uuid not null references realworld.user (id)
ON UPDATE RESTRICT
ON DELETE RESTRICT,
ON UPDATE CASCADE
ON DELETE CASCADE,
slug text not null unique,
title text not null,
description text not null default '',
body text not null default '',
deleted boolean not null default false
body text not null default ''
);

create index on realworld.article using btree(user_id);
Expand All @@ -57,11 +58,11 @@ create table realworld.favorite (
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
article_id uuid not null references realworld.article(id)
ON UPDATE RESTRICT
ON DELETE RESTRICT,
ON UPDATE CASCADE
ON DELETE CASCADE,
user_id uuid not null references realworld.user(id)
ON UPDATE RESTRICT
ON DELETE RESTRICT,
ON UPDATE CASCADE
ON DELETE CASCADE,
unique (article_id, user_id)
);

Expand All @@ -79,11 +80,11 @@ create table realworld.follow (
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
from_user_id uuid not null references realworld.user(id)
ON UPDATE RESTRICT
ON DELETE RESTRICT,
ON UPDATE CASCADE
ON DELETE CASCADE,
to_user_id uuid not null references realworld.user(id)
ON UPDATE RESTRICT
ON DELETE RESTRICT,
ON UPDATE CASCADE
ON DELETE CASCADE,
UNIQUE (from_user_id, to_user_id)
);

Expand Down Expand Up @@ -113,11 +114,11 @@ create table realworld.article_tag (
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
article_id uuid not null references realworld.article(id)
on update restrict
on delete restrict,
on update CASCADE
on delete CASCADE,
tag_id uuid not null references realworld.tag(id)
on update restrict
on delete restrict
on update CASCADE
on delete CASCADE
);


Expand All @@ -134,11 +135,11 @@ create table realworld.comment(
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
article_id uuid not null references realworld.article(id)
on update restrict
on delete restrict,
on update CASCADE
on delete CASCADE,
user_id uuid not null references realworld.user(id)
on update restrict
on delete restrict,
on update CASCADE
on delete CASCADE,
body text not null default '',
deleted boolean not null default false
);
Expand All @@ -148,25 +149,7 @@ CREATE TRIGGER set_realworld_comment_updated_at
FOR EACH ROW
EXECUTE PROCEDURE realworld.set_current_timestamp_updated_at();

create table realworld.api_key(
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
user_id uuid not null references realworld.user(id)
on update restrict
on delete restrict,
value text not null unique,
invalidated_at timestamptz
);

CREATE TRIGGER set_realworld_api_key_updated_at
BEFORE UPDATE ON realworld.api_key
FOR EACH ROW
EXECUTE PROCEDURE realworld.set_current_timestamp_updated_at();

create index on realworld.api_key using btree(value);

-- //@UNDO
DROP SCHEMA realworld cascade;

DROP EXTENSION citext;
-- DROP SCHEMA realworld cascade;
-- DROP EXTENSION citext;
-- DROP EXTENSION pgcrypto;
Loading

0 comments on commit f10b88e

Please sign in to comment.