readme files updated and ioservice-replacing-example has ben added

This commit is contained in:
stvort
2019-12-18 23:49:11 +04:00
parent de851657ae
commit 1c8de72adf
15 changed files with 402 additions and 1 deletions
+3 -1
View File
@@ -4,9 +4,11 @@
* *unit-testing-plain-spring* - пример тестирования в проектах на чистом Spring
* *unit-testing-spring-boot* - то же самое, только на Spring Boot
* *unit-testing-pure-spring-parallel* - пример "не удачного" и "работающего" варианта тестирования IOService в нескольких потоках
* *ioservice-replacing-example* - пример подмены консольного UI -> Swing UI, с помощью смены реализации IOService
* *hibernate-fetch-mode-demo* - демонстрация настроек Hibernate, в частности для решения проблемы N+1
* *mongo-db-demo* - демонстрация подходов к хранению вложенных сущностенй в MongoDB, работы с MongoEventListener, агрегациями и инструментом миграций Mongock
* *docker-test-containers* - пример настройки TestContainers для монги
* *spring-cloud-demo-stvort* - пример работы двух микросевисов с использованием Config server, Eureka, Zuul, Feign client
* *spring-mail-integration-demo* - пример работы с SpringMail через SpringIntegration
* *liquibase-demo* - пример работы с liquibase
* *liquibase-demo* - пример работы с liquibase
@@ -0,0 +1,25 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### 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/
@@ -0,0 +1,7 @@
## Пример подмены консольного UI -> Swing UI, с помощью смены реализации IOService
* *В примере есть класс проводящий опрос PollService и использующий для этого IOService*
* *В зависимости от настройки use.swing в application.yml создаются либо бины из пакета console, либо бины из пакета swing*
* *Т.к. один из этих бинов это реализация IOService, то получается либо консольный, либо оконный интерфейс программы без семы кода класса PollService*
* *Оконный интерфейс представлен классом PollMainForm, который взаимодействует с основной частью программы, через очереди в сервисе MessageSystem*
* *Через них же (с помощью IOService) основная часть программы взаимодействует с оконным интерфейсом*
@@ -0,0 +1,82 @@
<?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 http://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>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>ru.otus</groupId>
<artifactId>ioservice-replacing-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ioservice-replacing-example</name>
<description>IOService replacing example</description>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Для работы с Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Для тестирования Spring Boot, тут и mockito и AssertJ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Тестирование-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,13 @@
package ru.otus.ioservice.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class IOServiceExampleSpringBootApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(IOServiceExampleSpringBootApplication.class).headless(false).run(args);
}
}
@@ -0,0 +1,6 @@
package ru.otus.ioservice.example.api;
public interface IOService {
void out(String message);
String readString();
}
@@ -0,0 +1,31 @@
package ru.otus.ioservice.example.console;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
import java.io.PrintStream;
import java.util.Scanner;
@ConditionalOnProperty(name = "use.swing", havingValue = "false")
@Service
public class ConsoleIOService implements IOService {
private final PrintStream out;
private final Scanner sc;
public ConsoleIOService() {
this.out = System.out;
this.sc = new Scanner(System.in);
}
@Override
public void out(String message) {
out.println(message);
}
@Override
public String readString() {
return sc.nextLine();
}
}
@@ -0,0 +1,18 @@
package ru.otus.ioservice.example.console;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.ioservice.example.poll.PollService;
@ConditionalOnProperty(name = "use.swing", havingValue = "false")
@Configuration
public class UIConfig {
@Bean
public CommandLineRunner starter (PollService pollService) {
return args -> pollService.poll();
}
}
@@ -0,0 +1,29 @@
package ru.otus.ioservice.example.poll;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
@RequiredArgsConstructor
@Service
public class PollService {
private final IOService ioService;
public void poll() {
System.out.println("Началось!");
ioService.out("Как вас зовут?");
String name = ioService.readString();
ioService.out(String.format("Приятно познакомиться: %s", name));
ioService.out("Как ваши дела? Если хорошо, введите \"OK\"");
String res = ioService.readString();
if (res.equalsIgnoreCase("OK")) {
ioService.out("Это радует");
} else {
ioService.out("Ничего, скоро все наладится!");
}
ioService.out(String.format("До свидания %s", name));
}
}
@@ -0,0 +1,38 @@
package ru.otus.ioservice.example.swing;
import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import java.util.concurrent.LinkedBlockingQueue;
@Service
public class MessageSystem {
private final LinkedBlockingQueue<String> pollQueue;
private final LinkedBlockingQueue<String> uiQueue;
public MessageSystem() {
pollQueue = new LinkedBlockingQueue<>();
uiQueue = new LinkedBlockingQueue<>();
}
@SneakyThrows
public void putToPollQueue(String message) {
pollQueue.put(message);
}
@SneakyThrows
public void putToUiQueue(String message) {
uiQueue.put(message);
}
@SneakyThrows
public String takeFromPollQueue() {
return pollQueue.take();
}
@SneakyThrows
public String takeFromUiQueue() {
return uiQueue.take();
}
}
@@ -0,0 +1,86 @@
package ru.otus.ioservice.example.swing;
import lombok.RequiredArgsConstructor;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
@RequiredArgsConstructor
public class PollMainForm extends JFrame {
private final JPanel contentPane;
private final JLabel inLabel;
private final JTextField outTextField;
private final JButton outBtn;
private final MessageSystem ms;
private final AtomicBoolean runFlag;
private final ExecutorService uiFlow;
public PollMainForm(MessageSystem ms) throws HeadlessException {
super("Poll");
this.ms = ms;
runFlag = new AtomicBoolean(true);
uiFlow = Executors.newSingleThreadExecutor();
GridLayout layout = new GridLayout(3, 1);
contentPane = new JPanel(layout);
setContentPane(contentPane);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inLabel = new JLabel("");
contentPane.add(inLabel);
outTextField = new JTextField(15);
contentPane.add(outTextField);
outBtn = new JButton("Ответить");
outBtn.addActionListener(e -> ms.putToPollQueue(outTextField.getText()));
contentPane.add(outBtn);
setOnCloseHandler();
setSize(450, 100);
}
void init() {
setLocationRelativeTo(null);
setVisible(true);
startUiFlow();
}
private void startUiFlow() {
uiFlow.execute(() -> {
while (runFlag.get()) {
try {
String msg = ms.takeFromUiQueue();
synchronized (inLabel) {
inLabel.setText(msg);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
}
});
}
private void setOnCloseHandler() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
runFlag.set(false);
ms.putToUiQueue("exit");
uiFlow.shutdown();
e.getWindow().dispose();
}
});
}
}
@@ -0,0 +1,26 @@
package ru.otus.ioservice.example.swing;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
@ConditionalOnProperty(name = "use.swing", havingValue = "true")
@RequiredArgsConstructor
@Service
public class SwingIOService implements IOService {
private final MessageSystem ms;
@SneakyThrows
@Override
public void out(String message) {
ms.putToUiQueue(message);
}
@SneakyThrows
@Override
public String readString() {
return ms.takeFromPollQueue();
}
}
@@ -0,0 +1,30 @@
package ru.otus.ioservice.example.swing;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.ioservice.example.poll.PollService;
import java.awt.*;
@ConditionalOnProperty(name = "use.swing", havingValue = "true")
@Configuration
public class UIConfig {
@Bean
public CommandLineRunner starter(PollService pollService, MessageSystem ms) {
return args -> {
EventQueue.invokeLater(() -> {
try {
PollMainForm mainForm = new PollMainForm(ms);
mainForm.init();
} catch (Exception e) {
e.printStackTrace();
}
});
pollService.poll();
};
}
}
@@ -0,0 +1 @@
use.swing: false
@@ -0,0 +1,7 @@
## Пример "не удачного" и "работающего" варианта тестирования IOService в нескольких потоках
В примере демонстрируется:
* *возможность выполнения тестов в нескольких потоках (включается в junit-platform.properties\junit.jupiter.execution.parallel.enabled)*
* *частый подход к тестированию сервиса консольного ввода/вывода, через подмену System.in/out (ClosedIOServiceTest)*
* *проблемы возникающие при включении многопоточного режима исполнения тестов*
* *вариант решения проблем с многопоточным исполнением тестов, через получение потоков ввода/вывода в конструкторе сервиса (OpenedIOServiceTest)*