Skip to content

Commit

Permalink
promotion of postgres db, cors configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
vondacho committed May 10, 2023
1 parent 75ed2fd commit 49a3af4
Show file tree
Hide file tree
Showing 23 changed files with 135 additions and 88 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# arch-blueprint-java

![build workflow](https://github.com/vondacho/arch-blueprint-java/actions/workflows/build.yml/badge.svg)

A Java project as template and pedagogical support for the teaching of Clean Architecture crafting practice.

## Features

CRUD operations on Customer entities exposed by a REST API.

- Web request validation with [Swagger request validator](https://bitbucket.org/atlassian/swagger-request-validator/src/master/)
Expand All @@ -16,13 +14,13 @@ CRUD operations on Customer entities exposed by a REST API.
- Architecture testing with [ArchUnit](https://www.archunit.org/motivation)

## Getting started

- To build the project with `./gradlew clean build`.
- To launch the test suite with `./gradlew clean check`.
- To launch the application with `./gradlew bootRun --args='--spring.profiles.active=test,jpa'`.
- Build the project with `./gradlew clean build`.
- Launch the tests suite with `./gradlew clean check`.
- Start the database with `docker-compose up`.
- Launch the application with `./gradlew bootRun --args='--spring.profiles.active=test,jpa,postgres'`.
- Play use cases in Postman using [this default Postman collection](https://vondacho.github.io/arch-blueprint-java/postman/postman_collection.json).

## Technical documentation

- Powered by [MkDocs](https://www.mkdocs.org/getting-started/)
- API documentation powered by [Swagger UI](https://swagger.io/tools/swagger-ui/)
- Architecture documentation powered by [Structurizr](https://structurizr.com/) and [AppMap](https://appmap.io/docs/appmap-overview.html)
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ dependencies {

compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.postgresql:postgresql")

// testing
testImplementation("org.junit.platform:junit-platform-suite")
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ services:
container_name: postgres-blueprint
image: postgres:12-alpine
ports:
- 15432:5432
- "5432:5432"
environment:
- POSTGRES_DB=blueprint
- POSTGRES_USER=postgres
- POSTGRES_USER=blueprint
- POSTGRES_PASSWORD=blueprint
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TestUser {
public class TestWebUser {
public static final String TEST_USER_NAME = "test";
public static final String TEST_USER_PASSWORD = "test";
public static final String TEST_ADMIN_NAME = "admin";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import edu.obya.blueprint.customer.application.CustomerDto;
import edu.obya.blueprint.customer.at.TestUser;
import edu.obya.blueprint.customer.at.TestWebUser;
import edu.obya.blueprint.customer.at.infra.TestContext;
import edu.obya.blueprint.customer.domain.model.CustomerId;
import io.cucumber.java.en.When;
Expand Down Expand Up @@ -33,8 +33,8 @@ public void registeringTheNewCustomer(List<CustomerDto> attributes) throws Excep
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto))
.with(httpBasic(
TestContext.atOrDefault("userId", TestUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestUser.TEST_USER_PASSWORD)))
TestContext.atOrDefault("userId", TestWebUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestWebUser.TEST_USER_PASSWORD)))
));
entityManager.flush();
}
Expand All @@ -43,17 +43,17 @@ public void registeringTheNewCustomer(List<CustomerDto> attributes) throws Excep
public void listingAllExistingCustomers() throws Exception {
TestContext.set("result", mockMvc.perform(get("/customers")
.with(httpBasic(
TestContext.atOrDefault("userId", TestUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestUser.TEST_USER_PASSWORD)))
TestContext.atOrDefault("userId", TestWebUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestWebUser.TEST_USER_PASSWORD)))
));
}

@When("getting existing customer {customerId}")
public void gettingExistingCustomerWithId(CustomerId id) throws Exception {
TestContext.set("result", mockMvc.perform(get("/customers/" + id.getId())
.with(httpBasic(
TestContext.atOrDefault("userId", TestUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestUser.TEST_USER_PASSWORD)))
TestContext.atOrDefault("userId", TestWebUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestWebUser.TEST_USER_PASSWORD)))
));
}

Expand All @@ -64,8 +64,8 @@ public void replacingExistingCustomerWithId(CustomerId id, List<CustomerDto> att
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto))
.with(httpBasic(
TestContext.atOrDefault("userId", TestUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestUser.TEST_USER_PASSWORD)))
TestContext.atOrDefault("userId", TestWebUser.TEST_USER_NAME),
TestContext.atOrDefault("userPassword", TestWebUser.TEST_USER_PASSWORD)))
));
entityManager.flush();
}
Expand All @@ -74,8 +74,8 @@ public void replacingExistingCustomerWithId(CustomerId id, List<CustomerDto> att
public void removingExistingCustomerWithId(CustomerId id) throws Exception {
TestContext.set("result", mockMvc.perform(delete("/customers/" + id.getId())
.with(httpBasic(
TestContext.atOrDefault("userId", TestUser.TEST_ADMIN_NAME),
TestContext.atOrDefault("userPassword", TestUser.TEST_ADMIN_PASSWORD)))
TestContext.atOrDefault("userId", TestWebUser.TEST_ADMIN_NAME),
TestContext.atOrDefault("userPassword", TestWebUser.TEST_ADMIN_PASSWORD)))
));
entityManager.flush();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.util.UUID;
import java.util.function.Supplier;

import static edu.obya.blueprint.customer.at.TestUser.*;
import static edu.obya.blueprint.customer.at.TestWebUser.*;
import static edu.obya.blueprint.customer.at.infra.TestContext.set;
import static org.mockito.Mockito.when;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TestUserTokens {
public class TestWebUserTokens {
public static final String TEST_USER_TOKEN = "Basic dGVzdDp0ZXN0";
public static final String TEST_ADMIN_TOKEN = "Basic YWRtaW46YWRtaW4=";
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
import static edu.obya.blueprint.customer.adapter.rest.TestCustomerOut.TEST_CUSTOMER_OUT;
import static edu.obya.blueprint.customer.application.TestCustomerIn.TEST_CUSTOMER_IN;
import static edu.obya.blueprint.customer.domain.model.TestCustomer.*;
import static edu.obya.blueprint.customer.cdc.TestUserTokens.TEST_ADMIN_TOKEN;
import static edu.obya.blueprint.customer.cdc.TestUserTokens.TEST_USER_TOKEN;
import static edu.obya.blueprint.customer.cdc.TestWebUserTokens.TEST_ADMIN_TOKEN;
import static edu.obya.blueprint.customer.cdc.TestWebUserTokens.TEST_USER_TOKEN;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import static au.com.dius.pact.consumer.dsl.LambdaDsl.newJsonBody;
import static edu.obya.blueprint.customer.adapter.rest.TestCustomerOut.TEST_CUSTOMER_OUT;
import static edu.obya.blueprint.customer.domain.model.TestCustomer.TEST_CUSTOMER_ID;
import static edu.obya.blueprint.customer.cdc.TestUserTokens.TEST_USER_TOKEN;
import static edu.obya.blueprint.customer.cdc.TestWebUserTokens.TEST_USER_TOKEN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.cloud.contract.spec.internal.MediaTypes.APPLICATION_JSON_UTF8;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package edu.obya.blueprint.customer.cdc.spring.provider;

import edu.obya.blueprint.customer.domain.model.TestCustomer;
import edu.obya.blueprint.customer.cdc.TestUserTokens;
import edu.obya.blueprint.customer.cdc.TestWebUserTokens;
import edu.obya.blueprint.customer.domain.service.CustomerRepository;
import edu.obya.blueprint.customer.adapter.rest.CustomerController;
import io.micrometer.core.instrument.MeterRegistry;
Expand Down Expand Up @@ -55,10 +55,10 @@ String uriWithCustomerId() {
}

String userAuthToken() {
return TestUserTokens.TEST_USER_TOKEN;
return TestWebUserTokens.TEST_USER_TOKEN;
}

String adminAuthToken() {
return TestUserTokens.TEST_ADMIN_TOKEN;
return TestWebUserTokens.TEST_ADMIN_TOKEN;
}
}
23 changes: 5 additions & 18 deletions src/doc/tech/architecture.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
# Architecture
![build workflow](https://github.com/vondacho/arch-blueprint-java/actions/workflows/build.yml/badge.svg)

A Java project as template and pedagogical support for the teaching of Clean Architecture crafting practice.

## C4
- [System context](https://www.structurizr.com/share/38199/diagrams#blueprint-context)
- [Container view](https://www.structurizr.com/share/38199/diagrams#blueprint-containers)
- [Component view](https://www.structurizr.com/share/38199/diagrams#blueprint-api-components)

## AppMap

### From your local machine
To start the AppMap viewer web application on local port 3000 with
Start the AppMap viewer on local port 3000 with
`docker run -it -p 3000:8080 ghcr.io/vondacho/appmap-viewer:latest`.

To visualize the behaviour of main use cases from the browser at
`http://localhost:3000/appmap/appmap.html?appmap=<url_to_your_AppMap_file>`.

Then, visualize the behaviour of main use cases from the browser
- [E2E from API layer](http://localhost:3000/appmap/appmap.html?appmap=https://vondacho.github.io/arch-blueprint-java/appmap/edu_obya_blueprint_customer_adapter_rest_CustomerEndpointIT_shouldCreateAndModifyAndDeleteCustomer.appmap.json)
- [E2E from Service layer](http://localhost:3000/appmap/appmap.html?appmap=https://vondacho.github.io/arch-blueprint-java/appmap/edu_obya_blueprint_customer_application_CustomerServiceIT_shouldCreateAndFindAndModifyAndRemoveACustomer.appmap.json)
- [E2E from Data layer](http://localhost:3000/appmap/appmap.html?appmap=https://vondacho.github.io/arch-blueprint-java/appmap/edu_obya_blueprint_customer_adapter_jpa_CustomerRepositoryIT_shouldCreateAndFindAndModifyAndRemoveACustomer.appmap.json)

#### Local AppMap file
To start the AppMap viewer web application on local port 3000 with
`docker run -it -p 3000:8080 -v $(pwd):/usr/appmap-viewer/maps ghcr.io/vondacho/appmap-viewer:latest`.

To visualize your local AppMap file from the browser at
`http://localhost:3000/appmap/appmap.html?appmap=/maps/<your_AppMap_file>`.

### From your IDE
- To install the AppMap extension in your IDE.
- To build the app maps with `./gradlew appmap test`.
- To visualize the app maps using the AppMap extension inside your IDE.

## Hexagonal
The logical layers are organized like an onion with the domain layer at the center.
Infrastructure layer adapts the output ports exposed by the application and domain layers.
Expand Down
28 changes: 23 additions & 5 deletions src/doc/tech/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
# arch blueprint java
![build workflow](https://github.com/vondacho/arch-blueprint-java/actions/workflows/build.yml/badge.svg)

## Getting started
A Java project as template and pedagogical support for the teaching of Clean Architecture crafting practice.

## Features
CRUD operations on Customer entities exposed by a REST API.

- To build the project with `./gradlew clean build`.
- To launch the application with `./gradlew bootRun --args='--spring.profiles.active=test,jpa'`.
- To play use cases with Postman using [the default collection](postman/postman_collection.json).
- Web request validation with [Swagger request validator](https://bitbucket.org/atlassian/swagger-request-validator/src/master/)
- Web security based on Basic Authentication
- Application management with Spring Actuator
- Acceptance testing with [Cucumber](https://cucumber.io/docs/cucumber/)
- Contract testing with [Pact](https://docs.pact.io/) and [Spring Cloud Contract](https://softwaremill.com/contract-testing-spring-cloud-contract/)
- Architecture testing with [ArchUnit](https://www.archunit.org/motivation)

## Getting started
- Build the project with `./gradlew clean build`.
- Launch the tests suite with `./gradlew clean check`.
- Start the database with `docker-compose up`.
- Launch the application with `./gradlew bootRun --args='--spring.profiles.active=test,jpa,postgres'`.
- Play use cases in Postman using [this default Postman collection](https://vondacho.github.io/arch-blueprint-java/postman/postman_collection.json).

## Release
Draft new release of the application from GitHub [release panel](https://github.com/vondacho/arch-blueprint-java/releases).

- To release the application using GitHub release panel.
## This documentation
- Powered by [MkDocs](https://www.mkdocs.org/getting-started/)
- API documentation powered by [Swagger UI](https://swagger.io/tools/swagger-ui/)
- Architecture documentation powered by [Structurizr](https://structurizr.com/) and [AppMap](https://appmap.io/docs/appmap-overview.html)
18 changes: 7 additions & 11 deletions src/doc/tech/testing.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
# Testing

## Getting started

To launch the whole tests suite with `./gradlew clean check`.
Launch the whole tests suite with `./gradlew clean check`.

## Unit and integration testing
Launch the unit and integration tests with `./gradlew test`.

To launch the unit and integration tests with `./gradlew test`.
Record component interaction scenarios with `./gradlew appmap test`.

## Acceptance testing

To launch the acceptance tests with `./gradlew acceptanceTest`.
Launch the acceptance tests with `./gradlew acceptanceTest`.

## Contract testing

To launch the contract tests with `./gradlew contractTest`.
Launch the contract tests with `./gradlew contractTest`.

## Architecture testing

To launch the architecture tests with `./gradlew archTest`.
Launch the architecture tests with `./gradlew archTest`.

## Reporting

- [Cucumber](../reports/tests/cucumber)
- [Acceptance scenarios](../reports/tests/cucumber)
- [Acceptance test](../reports/tests/acceptanceTest)
- [Contract tests](../reports/tests/contractTest)
- [Unit and integration tests](../reports/tests/test)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.security.config.Customizer.withDefaults;

@EnableGlobalMethodSecurity(prePostEnabled=true)
Expand All @@ -24,17 +31,34 @@ public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(withDefaults())
.authorizeHttpRequests((authz) -> authz
.mvcMatchers("/customers", "/customers/**").hasAnyRole("USER","ADMIN")
.requestMatchers(EndpointRequest.to("health","info","metrics","loggers")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.anyRequest()
.authenticated()
)
.httpBasic(withDefaults())
.csrf().disable();
return http.build();
}

/**
* @return a CORS configuration for trying out the API from Swagger UI
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("http://localhost:*", "https://vondacho.github.io"));
configuration.setAllowedMethods(List.of("GET","POST","PUT", "DELETE", "OPTIONS", "HEAD"));
configuration.setAllowedHeaders(List.of(AUTHORIZATION, CONTENT_TYPE));
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/customers/**", configuration);
return source;
}

@Profile("dev")
@Bean
public UserDetailsService devUserDetailsService() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public class CustomerRepositoryJpaAdapter implements CustomerRepository {

@Override
public Optional<Customer> findById(@NonNull CustomerId customerId) {
return repository.findByLogicalId(customerId).map(this::toCustomer);
return repository.findByLogicalId(customerId).stream().findFirst().map(this::toCustomer);
}

@Override
public Optional<Customer> findByFirstNameAndLastName(String firstName, String lastName) {
return repository.findByFirstNameAndLastName(firstName, lastName).map(this::toCustomer);
return repository.findByFirstNameAndLastName(firstName, lastName).stream().findFirst().map(this::toCustomer);
}

@Override
Expand All @@ -52,15 +52,15 @@ public void add(@NonNull CustomerId id, @NonNull CustomerState state) {

@Override
public void update(@NonNull CustomerId customerId, @NonNull CustomerState customer) {
JpaCustomer data = repository.findByLogicalId(customerId)
JpaCustomer data = repository.findByLogicalId(customerId).stream().findFirst()
.orElseThrow(() -> new CustomerNotFoundException(customerId));
data.set(customer, customerId);
repository.save(data);
}

@Override
public void remove(@NonNull CustomerId customerId) {
JpaCustomer data = repository.findByLogicalId(customerId)
JpaCustomer data = repository.findByLogicalId(customerId).stream().findFirst()
.orElseThrow(() -> new CustomerNotFoundException(customerId));
repository.deleteById(data.getPk());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

Expand All @@ -14,6 +15,6 @@
public interface JpaCustomerRepository
extends PagingAndSortingRepository<JpaCustomer, UUID>, JpaSpecificationExecutor<JpaCustomer> {

Optional<JpaCustomer> findByLogicalId(CustomerId customerId);
Optional<JpaCustomer> findByFirstNameAndLastName(String firstName, String lastName);
List<JpaCustomer> findByLogicalId(CustomerId customerId);
List<JpaCustomer> findByFirstNameAndLastName(String firstName, String lastName);
}
Loading

0 comments on commit 49a3af4

Please sign in to comment.