Cześć wszystkim. Przypomnę: w pierwszej części dodaliśmy Flyway. Kontynuujmy.
Dodanie bazy danych do pliku docker-compose.yml
Kolejnym etapem jest ustawienie pracy z bazą danych w głównym pliku docker-compose.yml. Dodajmy bazę danych do pliku docker-compose:version: '3.1'
services:
jrtb-bot:
depends_on:
- jrtb-db
build:
context: .
environment:
BOT_NAME: ${BOT_NAME}
BOT_TOKEN: ${BOT_TOKEN}
BOT_DB_USERNAME: ${BOT_DB_USERNAME}
BOT_DB_PASSWORD: ${BOT_DB_PASSWORD}
restart: always
jrtb-db:
image: mysql:5.7
restart: always
environment:
MYSQL_USER: ${BOT_DB_USERNAME}
MYSQL_PASSWORD: ${BOT_DB_PASSWORD}
MYSQL_DATABASE: 'jrtb_db'
MYSQL_ROOT_PASSWORD: 'root'
ports:
- '3306:3306'
expose:
- '3306'
Dodałem także tę linię do naszej aplikacji:
depends_on:
- jrtb-db
Oznacza to, że przed uruchomieniem aplikacji czekamy na uruchomienie bazy danych. Następnie można zauważyć dodanie dwóch kolejnych zmiennych, które są nam potrzebne do pracy z bazą danych:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Pozyskamy je w docker-compose w taki sam sposób, jak w przypadku bota telegramu - poprzez zmienne środowiskowe. Zrobiłem to tak, że mamy tylko jedno miejsce, w którym ustawiamy wartości nazwy użytkownika bazy danych i jego hasła. Przekazujemy je do obrazu dokującego naszej aplikacji oraz do kontenera dokowanego naszej bazy danych. Następnie musimy zaktualizować plik Dockerfile, aby nauczyć naszego SpringBoota akceptowania zmiennych dla bazy danych.
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"]
Teraz dodajemy zmienne bazy danych do pliku Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Wartości zmiennych będą inne. Te, które przekażemy do pliku Dockerfile, wymagają jednak wartości domyślnych, więc je wprowadziłem. Ostatnią linię rozwijamy o dwa elementy, za pomocą których przekażemy nazwę użytkownika i hasło DB do uruchomienia aplikacji:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Ostatnia linia pliku Dockerfile (zaczynająca się od ENTRYPOINT) musi być bez zawijania. Jeśli wykonasz przelew, ten kod nie zadziała. Ostatnim krokiem jest aktualizacja pliku start.sh w celu przekazania zmiennych do bazy danych.
#!/bin/bash
# Pull new changes
git pull
# Prepare Jar
mvn clean
mvn package
# Ensure, that docker-compose stopped
docker-compose stop
# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
# Start new deployment
docker-compose up --build -d
Wiemy już, jak dodać zmienne środowiskowe przed uruchomieniem docker-compose. Aby to zrobić, wystarczy wykonać eksport nazwa_zmiennej=wartość_zmiennej.. Dlatego dodajemy tylko dwie linie:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Tutaj ustawiamy nazwę użytkownika i hasło bazy danych. Oczywiście możliwe byłoby przekazanie tych zmiennych podczas uruchamiania skryptu bash, tak jak robimy to w przypadku nazwy i tokena bota. Wydaje mi się jednak, że jest to niepotrzebne. Aby faktycznie uzyskać dostęp do bazy danych, należy znać adres IP serwera, na którym baza danych będzie wdrożona oraz znajdować się na liście dozwolonych adresów IP dla żądania. Jak dla mnie to już wystarczy. Fundamenty zostały położone: teraz możesz robić rzeczy bardziej zrozumiałe dla programisty - pisać kod. Wcześniej robiliśmy to, co robią inżynierowie DevOps – konfigurowaliśmy środowisko.
Dodanie warstwy Repozytorium
Zazwyczaj aplikacja składa się z trzech warstw:- Kontrolery są punktami wejścia do aplikacji.
- Usługi to miejsce, w którym działa logika biznesowa. Częściowo już to mamy: SendMessageService jest wyraźnym przedstawicielem logiki biznesowej.
- Repozytoria to miejsce pracy z bazą danych. W naszym przypadku jest to bot telegramowy.
- Nie będziemy musieli pracować z JDBC: będziemy pracować bezpośrednio z wyższymi abstrakcjami. Oznacza to, że przechowuj POJO odpowiadające tabelom w bazie danych. Takie klasy będziemy nazywać encja , tak jak się je oficjalnie nazywa w Java Persistence API (jest to powszechny zestaw interfejsów do pracy z bazą danych poprzez ORM, czyli abstrakcja w stosunku do pracy z JDBC). Będziemy mieli klasę encji, którą zapiszemy w bazie danych i zostaną one zapisane dokładnie w tej tabeli, której potrzebujemy. Te same obiekty otrzymamy podczas wyszukiwania w bazie danych.
- Spring Data sugeruje użycie ich zestawu interfejsów: JpaRepository , CrudRepository itp. Istnieją inne interfejsy: pełną listę można znaleźć tutaj . Piękno polega na tym, że można używać ich metod bez ich wdrażania(!). Ponadto istnieje pewien szablon, za pomocą którego można pisać nowe metody w interfejsie, a będą one wdrażane automatycznie.
- Wiosna maksymalnie ułatwia nam rozwój. Aby to zrobić musimy stworzyć własny interfejs i dziedziczyć z tych opisanych powyżej. I żeby Spring wiedział, że musi skorzystać z tego interfejsu, dodaj adnotację Repozytorium.
- Jeśli będziemy musieli napisać metodę pracy z bazą danych, która nie istnieje, to również nie stanowi to problemu - napiszemy to. Pokażę Ci co i jak tam zrobić.
package com.github.javarushcommunity.jrtb.repository.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Telegram User entity.
*/
@Data
@Entity
@Table(name = "tg_user")
public class TelegramUser {
@Id
@Column(name = "chat_id")
private String chatId;
@Column(name = "active")
private boolean active;
}
Tutaj możesz zobaczyć, że mamy wszystkie adnotacje z pakietu javax.persistence. Są to typowe adnotacje używane we wszystkich implementacjach ORM. Domyślnie Spring Data Jpa używa Hibernacji, chociaż można zastosować inne implementacje. Oto lista adnotacji, których używamy:
- Jednostka - wskazuje, że jest to jednostka do pracy z bazą danych;
- Tabela - tutaj definiujemy nazwę tabeli;
- Id - adnotacja informuje, które pole będzie kluczem podstawowym w tabeli;
- Kolumna - określ nazwę pola z tabeli.
package com.github.javarushcommunity.jrtb.repository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* {@link Repository} for handling with {@link TelegramUser} entity.
*/
@Repository
public interface TelegramUserRepository extends JpaRepository<TelegramUser, String> {
List<TelegramUser> findAllByActiveTrue();
}
Tutaj możesz zobaczyć, jak dodałem metodę findAllByActiveTrue() , której nigdzie nie implementuję. Ale to nie przeszkodzi mu w pracy. Spring Data zrozumie, że musi pobrać wszystkie rekordy z tabeli tg_user, której aktywne pole = true . Dodajemy usługę współpracy z jednostką TelegramUser (inwersję zależności z SOLID stosujemy w kontekście, że usługi innych podmiotów nie mogą bezpośrednio komunikować się z repozytorium innego podmiotu - jedynie poprzez usługę tego podmiotu). W pakiecie tworzymy usługę TelegramUserService, która na razie będzie miała kilka metod: zapisanie użytkownika, pobranie użytkownika po jego ID i wyświetlenie listy aktywnych użytkowników. Najpierw tworzymy interfejs TelegramUserService:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* {@link Service} for handling {@link TelegramUser} entity.
*/
public interface TelegramUserService {
/**
* Save provided {@link TelegramUser} entity.
*
* @param telegramUser provided telegram user.
*/
void save(TelegramUser telegramUser);
/**
* Retrieve all active {@link TelegramUser}.
*
* @return the collection of the active {@link TelegramUser} objects.
*/
List<TelegramUser> retrieveAllActiveUsers();
/**
* Find {@link TelegramUser} by chatId.
*
* @param chatId provided Chat ID
* @return {@link TelegramUser} with provided chat ID or null otherwise.
*/
Optional<TelegramUser> findByChatId(String chatId);
}
I w rzeczywistości implementacja TelegramUserServiceImpl:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.repository.TelegramUserRepository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* Implementation of {@link TelegramUserService}.
*/
@Service
public class TelegramUserServiceImpl implements TelegramUserService {
private final TelegramUserRepository telegramUserRepository;
@Autowired
public TelegramUserServiceImpl(TelegramUserRepository telegramUserRepository) {
this.telegramUserRepository = telegramUserRepository;
}
@Override
public void save(TelegramUser telegramUser) {
telegramUserRepository.save(telegramUser);
}
@Override
public List<TelegramUser> retrieveAllActiveUsers() {
return telegramUserRepository.findAllByActiveTrue();
}
@Override
public Optional<TelegramUser> findByChatId(String chatId) {
return telegramUserRepository.findById(chatId);
}
}
Tutaj należy zaznaczyć, że używamy wstrzykiwania zależności (wprowadzamy instancję klasy) obiektu TelegramuserRepository za pomocą adnotacji Autowired oraz na konstruktorze. Możesz to zrobić dla zmiennej, ale jest to podejście, które zaleca nam zespół Spring Framework.
Dodawanie statystyk dla bota
Następnie musisz zaktualizować polecenia /start i /stop. W przypadku użycia polecenia /start należy zapisać nowego użytkownika w bazie danych i ustawić dla niego wartość aktywny = prawda. A gdy jest /stop, zaktualizuj dane użytkownika: set active = false. Naprawmy klasę StartCommand :package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Start {@link Command}.
*/
public class StartCommand implements Command {
private final SendBotMessageService sendBotMessageService;
private final TelegramUserService telegramUserService;
public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
"статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";
public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
String chatId = update.getMessage().getChatId().toString();
telegramUserService.findByChatId(chatId).ifPresentOrElse(
user -> {
user.setActive(true);
telegramUserService.save(user);
},
() -> {
TelegramUser telegramUser = new TelegramUser();
telegramUser.setActive(true);
telegramUser.setChatId(chatId);
telegramUserService.save(telegramUser);
});
sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
}
Tutaj również przekazujemy konstruktorowi obiekt TelegramuserService, za pomocą którego zapiszemy nowego użytkownika. Co więcej, korzystając z uroków Opcjonalnego w Javie, działa następująca logika: jeśli mamy użytkownika w bazie danych, po prostu go aktywujemy, jeśli nie, tworzymy nowego aktywnego. Polecenie zatrzymania:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.Optional;
/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {
private final SendBotMessageService sendBotMessageService;
private final TelegramUserService telegramUserService;
public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";
public StopCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
telegramUserService.findByChatId(update.getMessage().getChatId().toString())
.ifPresent(it -> {
it.setActive(false);
telegramUserService.save(it);
});
}
}
W ten sam sposób przekazujemy TelegramServiceTest do StopCommand. Dodatkowa logika jest taka: jeśli mamy użytkownika z takim ID czatu, dezaktywujemy go, czyli ustawiamy active = false. Jak możesz to zobaczyć na własne oczy? Stwórzmy nowe polecenie /stat, które wyświetli statystyki bota. Na tym etapie będą to proste statystyki dostępne dla wszystkich użytkowników. W przyszłości ograniczymy to i udostępnimy jedynie administratorom. W statystykach będzie jeden wpis: liczba aktywnych użytkowników bota. Aby to zrobić, dodaj wartość STAT("/stat") do CommandName. Następnie utwórz klasę StatCommand :
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Statistics {@link Command}.
*/
public class StatCommand implements Command {
private final TelegramUserService telegramUserService;
private final SendBotMessageService sendBotMessageService;
public final static String STAT_MESSAGE = "Javarush Telegram Bot использует %s человек.";
@Autowired
public StatCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
int activeUserCount = telegramUserService.retrieveAllActiveUsers().size();
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), String.format(STAT_MESSAGE, activeUserCount));
}
}
Tutaj wszystko jest proste: za pomocą metody retrieveAllActiveUsers uzyskujemy listę wszystkich aktywnych użytkowników i uzyskujemy wielkość kolekcji. Musimy teraz także zaktualizować klasy rosnące: CommandContainer i JavarushTelegramBot , aby nauczyły się przenosić nową usługę, której potrzebujemy. Kontener poleceń:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import com.google.common.collect.ImmutableMap;
import static com.github.javarushcommunity.jrtb.command.CommandName.*;
/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {
private final ImmutableMap<String, Command> commandMap;
private final Command unknownCommand;
public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
commandMap = ImmutableMap.<String, Command>builder()
.put(START.getCommandName(), new StartCommand(sendBotMessageService, telegramUserService))
.put(STOP.getCommandName(), new StopCommand(sendBotMessageService, telegramUserService))
.put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
.put(NO.getCommandName(), new NoCommand(sendBotMessageService))
.put(STAT.getCommandName(), new StatCommand(sendBotMessageService, telegramUserService))
.build();
unknownCommand = new UnknownCommand(sendBotMessageService);
}
public Command retrieveCommand(String commandIdentifier) {
return commandMap.getOrDefault(commandIdentifier, unknownCommand);
}
}
Tutaj dodaliśmy nowe polecenie do mapy i przekazaliśmy je przez konstruktor TelegramUserService. Ale w samym bocie zmieni się tylko konstruktor:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Teraz przekazujemy TelegramUserService jako argument, dodając adnotację Autowired. Oznacza to, że otrzymamy go z Kontekstu Aplikacji. Zaktualizujemy także klasę HelpCommand tak, aby w opisie pojawiło się nowe polecenie statystyczne.
Testowanie ręczne
Uruchommy bazę danych z pliku docker-compose-test.yml i metody głównej w klasie JavarushTelegramBotApplication. Następnie piszemy zestaw poleceń:- /stat - spodziewamy się, że jeśli baza danych będzie pusta, nie będzie żadnych osób korzystających z tego bota;
- /start – uruchamia bota;
- /stat - teraz oczekujemy, że z bota będzie korzystać 1 osoba;
- /stop – zatrzymuje bota;
- /stat - spodziewamy się, że ponownie będzie z niego korzystać 0 osób.
Piszemy i aktualizujemy testy
Ponieważ zmieniliśmy konstruktory, będziemy musieli zaktualizować także klasy testowe. W klasie AbstractCommandTest musimy dodać jeszcze jedno pole - klasę telegramUserService , która jest potrzebna dla trzech poleceń:protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Następnie zaktualizujmy metodę init() w CommandContainerze :
@BeforeEach
public void init() {
SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
W StartCommand musisz zaktualizować metodę getCommand() :
@Override
Command getCommand() {
return new StartCommand(sendBotMessageService, telegramUserService);
}
Również w StopCommand:
@Override
Command getCommand() {
return new StopCommand(sendBotMessageService, telegramUserService);
}
Następnie przyjrzyjmy się nowym testom. Stwórzmy typowy test dla StatCommand :
package com.github.javarushcommunity.jrtb.command;
import static com.github.javarushcommunity.jrtb.command.CommandName.STAT;
import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE;
public class StatCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return STAT.getCommandName();
}
@Override
String getCommandMessage() {
return String.format(STAT_MESSAGE, 0);
}
@Override
Command getCommand() {
return new StatCommand(sendBotMessageService, telegramUserService);
}
}
To jest proste. Porozmawiajmy teraz o tym, jak przetestujemy pracę z bazą danych. Wszystko, co zrobiliśmy wcześniej, to testy jednostkowe. Test integracyjny sprawdza integrację pomiędzy wieloma częściami aplikacji. Na przykład aplikacje i bazy danych. Tutaj wszystko będzie bardziej skomplikowane, ponieważ do testów potrzebujemy wdrożonej bazy danych. Dlatego też, gdy uruchamiamy nasze testy lokalnie, baza danych musi działać w pliku docker-compose-test.yml. Aby uruchomić ten test, musisz uruchomić całą aplikację SpringBoot. Klasa testowa posiada adnotację SpringBootTest , która uruchomi aplikację. Ale to podejście nie będzie dla nas skuteczne, ponieważ po uruchomieniu aplikacji uruchomi się także bot telegramu. Ale jest tu sprzeczność. Testy będą przeprowadzane zarówno lokalnie na naszej maszynie, jak i publicznie poprzez GitHub Actions. Aby testy przebiegły pomyślnie wraz z uruchomieniem całej aplikacji, musimy je uruchomić z prawidłowymi danymi na bocie telegramowym: czyli po jego nazwie i tokenie... W związku z tym mamy dwie opcje:
- Zatem upublicznij nazwę i token bota i miej nadzieję, że wszystko będzie dobrze, nikt nie będzie z niego korzystał i nam przeszkadzał.
- Wymyśl inny sposób.
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
U nas będą one zlokalizowane wzdłuż ścieżki ./src/test/resources/ + ścieżka podana w adnotacji. Oto jak wyglądają:
clearDbs.sql:
DELETE FROM tg_user;
telegram_users.sql:
INSERT INTO tg_user VALUES ("123456789", 1);
INSERT INTO tg_user VALUES ("123456788", 1);
INSERT INTO tg_user VALUES ("123456787", 1);
INSERT INTO tg_user VALUES ("123456786", 1);
INSERT INTO tg_user VALUES ("123456785", 1);
INSERT INTO tg_user VALUES ("123456784", 0);
INSERT INTO tg_user VALUES ("123456782", 0);
INSERT INTO tg_user VALUES ("123456781", 0);
Tak w rezultacie będzie wyglądał nasz test TelegramUserRepositoryIT (jak widać, dla testów integracyjnych nazwa będzie inna - dodajemy IT, a nie Test):
package com.github.javarushcommunity.jrtb.repository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import java.util.List;
import java.util.Optional;
import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;
/**
* Integration-level testing for {@link TelegramUserRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class TelegramUserRepositoryIT {
@Autowired
private TelegramUserRepository telegramUserRepository;
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
@Test
public void shouldProperlyFindAllActiveUsers() {
//when
List<TelegramUser> users = telegramUserRepository.findAllByActiveTrue();
//then
Assertions.assertEquals(5, users.size());
}
@Sql(scripts = {"/sql/clearDbs.sql"})
@Test
public void shouldProperlySaveTelegramUser() {
//given
TelegramUser telegramUser = new TelegramUser();
telegramUser.setChatId("1234567890");
telegramUser.setActive(false);
telegramUserRepository.save(telegramUser);
//when
Optional<TelegramUser> saved = telegramUserRepository.findById(telegramUser.getChatId());
//then
Assertions.assertTrue(saved.isPresent());
Assertions.assertEquals(telegramUser, saved.get());
}
}
Pisaliśmy testy, ale pojawia się pytanie: co będzie z uruchomieniem naszego procesu CI na GitHubie? Nie będzie mieć bazy danych. Na razie będzie tylko wersja czerwona. W tym celu mamy akcje GitHub, w których możemy skonfigurować uruchomienie naszego buildu. Przed uruchomieniem testów należy dodać uruchomienie bazy danych z niezbędnymi ustawieniami. Jak się okazuje, przykładów w Internecie nie ma zbyt wiele, dlatego radzę gdzieś to zapisać. Zaktualizujmy plik .github/workflows/maven.yml:
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up MySQL
uses: mirromutth/mysql-action@v1.1
with:
mysql version: '5.7'
mysql database: 'dev_jrtb_db'
mysql root password: 'root'
mysql user: 'dev_jrtb_db_user'
mysql password: 'dev_jrtb_db_password'
- name: Set up JDK 1.11
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Build with Maven
run: mvn -B package --file pom.xml
Teraz pojawił się nowy blok Konfiguruj MySQL . Dodajemy w nim MySQL do naszego procesu CI, jednocześnie definiując potrzebne nam zmienne. Teraz dodaliśmy wszystko, co chcieliśmy. Ostatnim etapem jest wypchnięcie zmian i sprawdzenie, czy kompilacja przejdzie i będzie ekologiczna.
Aktualizacja dokumentacji
Zaktualizujmy wersję projektu z 0.3.0-SNAPSHOT do 0.4.0-SNAPSHOT w pom.xml i dodajmy także do RELEASE_NOTES:## 0.4.0-SNAPSHOT
* JRTB-1: added repository layer.
Po tym wszystkim tworzymy żądanie zatwierdzenia, wypychania i ściągania. A co najważniejsze, nasza konstrukcja jest ekologiczna!
Przydatne linki:
- Repozytorium naszego bota telegramowego
- Pull request ze wszystkimi zmianami opisanymi w artykule
- Artykuł SpringBoot + Flyway
- Obraz MySQL z DockerHub
- Medium: Jak utworzyć instancję MySql za pomocą Docker Compose
- Habr: Spring Data Jpa
- Mój kanał telegramowy
GO TO FULL VERSION