Skip to content
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
16 changes: 16 additions & 0 deletions .github/workflows/java.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Java CI
on: [push]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up JDK 23
uses: actions/setup-java@v2
with:
java-version: '23'
distribution: 'temurin'
- name: Build with Maven
run: mvn --batch-mode --update-snapshots package
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,53 @@
init
# Service for owners and their cats

Pet project - Async REST API to interact with owners and their cats.

### Technologies

* <b>Spring MVC</b>, <b>Spring Boot</b>
* <b>Spring Data JPA</b>: repositories
* <b>Spring Security</b> + <b>JWT</b>: all endpoints except login and user creation are secured. After successful login, the access token is stored in the cookies
* <b>Kafka</b> - message broker. Communication between 3 microservices. One microservice is external, receives requests, sends messages through the broker to two other microservices (for logic related to cats and owners), receives responses, and sends them in the required format. Each microservice has its own database
* Testing
* integration tests using <b>Testcontainers</b> for repositories
* <b>JUint5</b> and <b>Mockito</b> for services unit tests
* <b>mockMvc</b> for controllers tests
* <b>Docker</b>, <b>Docker Compose</b>, <b>Flyway</b>: start database containers, broker(Kafka), microservices, pgAdmin, Kafka UI, Zookeeper, and apply migrations.
* <b>Swagger</b>: documentation
* <b>CI CD</b>: run tests

Databases:

![schema.png](images/schema.png)

Communication between microservices:

![communication.png](images/communication.png)

### Functional:

* User:
* create (with Owner)
* login
* logout

* Owner
* get info
* delete
* get owners with several criteria (by name/birthday; only for admin)
* delete all owners (only for admin)

* Cat
* create
* get info
* get owners with several criteria (name/birthday/breed/color/eyes color). Admin will get all cats, owners only their cats
* change owner
* adding and removing cat to friends list
* delete
* delete all cats (only for admin)

### TODO

* Store and use refresh token

* Testing microservices communication with Testcontainers (Kafka)
33 changes: 33 additions & 0 deletions catMicroservice/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

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

### VS Code ###
.vscode/
6 changes: 6 additions & 0 deletions catMicroservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

FROM openjdk:21-jdk-slim

COPY target/catMicroservice-0.0.1-SNAPSHOT.jar catMicroservice-0.0.1-SNAPSHOT.jar

ENTRYPOINT [ "java", "-jar" , "catMicroservice-0.0.1-SNAPSHOT.jar" ]
130 changes: 130 additions & 0 deletions catMicroservice/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>labs</groupId>
<artifactId>catMicroservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>catMicroservice</name>
<description>catMicroservice</description>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.20.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>labs</groupId>
<artifactId>customexceptions</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>labs</groupId>
<artifactId>dto</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package labs.catmicroservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CatMicroserviceApplication {

public static void main(String[] args) {
SpringApplication.run(CatMicroserviceApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package labs.catmicroservice.kafka;

import labs.*;
import labs.catmicroservice.persistence.CatRepository;
import labs.catmicroservice.service.CatService;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CatListener {
@Autowired
private CatService catService;

@KafkaListener(topics = "create_cat_request", groupId = "group1",
containerFactory = "createCatRequestListenerContainerFactory")
@SendTo
public CatDTO processCreateCat(CreateCatDTO data) {
System.out.println("Want to create cat");

return catService.createCat(data);
}

@KafkaListener(topics = "get_cat_request", groupId = "group1",
containerFactory = "getCatRequestListenerContainerFactory")
@SendTo
public GetCatDTO processGetCat(Long id) {
System.out.println("Want to get cat");

try {
return new GetCatDTO(catService.getCat(id), "");
}
catch (CatNotFoundException e) {
return new GetCatDTO(null, e.getMessage());
}
}

@KafkaListener(topics = "get_all_cats_request", groupId = "group1",
containerFactory = "getAllCatsRequestListenerContainerFactory")
@SendTo
public GetAllCatsResponse processGetAllCats(GetAllCatsRequest getAllCatsRequest) {
System.out.println("Want to get all cats");

List<CatDTO> page = catService.getCats(
getAllCatsRequest.catCriteriaDTO(),
getAllCatsRequest.page(),
getAllCatsRequest.size())
.stream()
.toList();

System.out.println("page" + page);

return new GetAllCatsResponse(page);
}

@KafkaListener(topics = "make_friends_request", groupId = "group1",
containerFactory = "friendsRequestListenerContainerFactory")
@SendTo
public CatsFriendsResponse processMakeFriends(CatsFriendsRequest catsFriendsRequest) {
System.out.println("Want to make friends");
String errorMessage = "";
try {
catService.makeFriends(catsFriendsRequest.firstId(), catsFriendsRequest.secondId());
}
catch (Exception e) {
errorMessage = e.getMessage();
}

return new CatsFriendsResponse(errorMessage);
}

@KafkaListener(topics = "unmake_friends_request", groupId = "group1",
containerFactory = "friendsRequestListenerContainerFactory")
@SendTo
public CatsFriendsResponse processUnmakeFriends(CatsFriendsRequest catsFriendsRequest) {
String errorMessage = "";
try {
catService.unmakeFriends(catsFriendsRequest.firstId(), catsFriendsRequest.secondId());
}
catch (Exception e) {
errorMessage = e.getMessage();
}

return new CatsFriendsResponse(errorMessage);
}

@KafkaListener(topics = "change_owner_request", groupId = "group1",
containerFactory = "changeOwnerRequestListenerContainerFactory")
@SendTo
public ChangeOwnerResponse processChangeOwner(ChangeOwnerRequest changeOwnerRequest) {
String errorMessage = "";
try {
catService.changeOwner(changeOwnerRequest.catId(), changeOwnerRequest.newOwnerId());
}
catch (Exception e) {
errorMessage = e.getMessage();
}

return new ChangeOwnerResponse(errorMessage);
}

@KafkaListener(topics = "delete_cat_by_id", groupId = "group1", containerFactory = "requestListenerContainerFactory")
public void processDeleteOwnerById(@Payload Long catId) {
System.out.println("Want to delete cat by id: " + catId);
catService.deleteCat(catId);
}

@KafkaListener(topics = "delete_all_cats", groupId = "group1")
public void processDeleteAllOwners(@Payload String s) {
System.out.println("Want to delete all: ");
catService.deleteCats();
}
}
Loading