add new package

This commit is contained in:
YuCheng Hu 2024-04-30 11:54:43 -04:00
parent 6b33c0d65d
commit 99259d231b
No known key found for this signature in database
GPG Key ID: 942395299055675C
291 changed files with 7240 additions and 0 deletions

1
patterns-modules/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/product-service/

View File

@ -0,0 +1,4 @@
## Patterns Modules
This module contains articles about design patterns.

View File

@ -0,0 +1,4 @@
### Relevant Articles:
- [Clean Architecture with Spring Boot](https://www.baeldung.com/spring-boot-clean-architecture)
- [Anemic vs. Rich Domain Objects](https://www.baeldung.com/java-anemic-vs-rich-domain-objects)

View File

@ -0,0 +1,63 @@
<?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>
<artifactId>clean-architecture</artifactId>
<version>1.0</version>
<name>clean-architecture</name>
<description>Project for clean architecture in java</description>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
package com.baeldung.pattern.cleanarchitecture;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
@SpringBootApplication
public class CleanArchitectureApplication {
public static void main(String[] args) {
SpringApplication.run(CleanArchitectureApplication.class);
}
@Bean
BeanFactoryPostProcessor beanFactoryPostProcessor(ApplicationContext beanRegistry) {
return beanFactory -> {
genericApplicationContext((BeanDefinitionRegistry) ((AnnotationConfigServletWebServerApplicationContext) beanRegistry).getBeanFactory());
};
}
void genericApplicationContext(BeanDefinitionRegistry beanRegistry) {
ClassPathBeanDefinitionScanner beanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanRegistry);
beanDefinitionScanner.addIncludeFilter(removeModelAndEntitiesFilter());
beanDefinitionScanner.scan("com.baeldung.pattern.cleanarchitecture");
}
static TypeFilter removeModelAndEntitiesFilter() {
return (MetadataReader mr, MetadataReaderFactory mrf) -> !mr.getClassMetadata()
.getClassName()
.endsWith("Model");
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
class CommonUser implements User {
String name;
String password;
CommonUser(String name, String password) {
this.name = name;
this.password = password;
}
CommonUser() {
}
@Override
public boolean passwordIsValid() {
return password != null && password.length() > 5;
}
@Override
public String getName() {
return name;
}
@Override
public String getPassword() {
return password;
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
class CommonUserFactory implements UserFactory {
@Override
public User create(String name, String password) {
return new CommonUser(name, password);
}
}

View File

@ -0,0 +1,21 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
class JpaUser implements UserRegisterDsGateway {
final JpaUserRepository repository;
JpaUser(JpaUserRepository repository) {
this.repository = repository;
}
@Override
public boolean existsByName(String name) {
return repository.existsById(name);
}
@Override
public void save(UserDsRequestModel requestModel) {
UserDataMapper accountDataMapper = new UserDataMapper(requestModel.getName(), requestModel.getPassword(), requestModel.getCreationTime());
repository.save(accountDataMapper);
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
interface JpaUserRepository extends JpaRepository<UserDataMapper, String> {
}

View File

@ -0,0 +1,9 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
interface User {
boolean passwordIsValid();
String getName();
String getPassword();
}

View File

@ -0,0 +1,53 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user")
class UserDataMapper {
@Id
String name;
String password;
LocalDateTime creationTime;
public UserDataMapper() {
}
public UserDataMapper(String name, String password, LocalDateTime creationTime) {
super();
this.name = name;
this.password = password;
this.creationTime = creationTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public LocalDateTime getCreationTime() {
return creationTime;
}
public void setCreationTime(LocalDateTime creationTime) {
this.creationTime = creationTime;
}
}

View File

@ -0,0 +1,41 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import java.time.LocalDateTime;
class UserDsRequestModel {
String name;
String password;
LocalDateTime creationTime;
public UserDsRequestModel(String name, String password, LocalDateTime creationTime) {
this.name = name;
this.password = password;
this.creationTime = creationTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public LocalDateTime getCreationTime() {
return creationTime;
}
public void setCreationTime(LocalDateTime creationTime) {
this.creationTime = creationTime;
}
}

View File

@ -0,0 +1,5 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
interface UserFactory {
User create(String name, String password);
}

View File

@ -0,0 +1,5 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
public interface UserInputBoundary {
UserResponseModel create(UserRequestModel requestModel);
}

View File

@ -0,0 +1,7 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
interface UserPresenter {
UserResponseModel prepareSuccessView(UserResponseModel user);
UserResponseModel prepareFailView(String error);
}

View File

@ -0,0 +1,20 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
class UserRegisterController {
final UserInputBoundary userInput;
UserRegisterController(UserInputBoundary accountGateway) {
this.userInput = accountGateway;
}
@PostMapping("/user")
UserResponseModel create(@RequestBody UserRequestModel requestModel) {
return userInput.create(requestModel);
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
interface UserRegisterDsGateway {
boolean existsByName(String identifier);
void save(UserDsRequestModel requestModel);
}

View File

@ -0,0 +1,35 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import java.time.LocalDateTime;
class UserRegisterInteractor implements UserInputBoundary {
final UserRegisterDsGateway userDsGateway;
final UserPresenter userPresenter;
final UserFactory userFactory;
UserRegisterInteractor(UserRegisterDsGateway userRegisterDfGateway, UserPresenter userPresenter,
UserFactory userFactory) {
this.userDsGateway = userRegisterDfGateway;
this.userPresenter = userPresenter;
this.userFactory = userFactory;
}
@Override
public UserResponseModel create(UserRequestModel requestModel) {
if (userDsGateway.existsByName(requestModel.getName())) {
return userPresenter.prepareFailView("User already exists.");
}
User user = userFactory.create(requestModel.getName(), requestModel.getPassword());
if (!user.passwordIsValid()) {
return userPresenter.prepareFailView("User password must have more than 5 characters.");
}
LocalDateTime now = LocalDateTime.now();
UserDsRequestModel userDsModel = new UserDsRequestModel(user.getName(), user.getPassword(), now);
userDsGateway.save(userDsModel);
UserResponseModel accountResponseModel = new UserResponseModel(user.getName(), now.toString());
return userPresenter.prepareSuccessView(accountResponseModel);
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
class UserRequestModel {
String name;
String password;
public UserRequestModel() {
super();
}
UserRequestModel(String name, String password) {
super();
this.name = name;
this.password = password;
}
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
String getPassword() {
return password;
}
void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
class UserResponseFormatter implements UserPresenter {
@Override
public UserResponseModel prepareSuccessView(UserResponseModel response) {
LocalDateTime responseTime = LocalDateTime.parse(response.getCreationTime());
response.setCreationTime(responseTime.format(DateTimeFormatter.ofPattern("hh:mm:ss")));
return response;
}
@Override
public UserResponseModel prepareFailView(String error) {
throw new ResponseStatusException(HttpStatus.CONFLICT, error);
}
}

View File

@ -0,0 +1,29 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
public class UserResponseModel {
String login;
String creationTime;
public UserResponseModel(String login, String creationTime) {
this.login = login;
this.creationTime = creationTime;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getCreationTime() {
return creationTime;
}
public void setCreationTime(String creationTime) {
this.creationTime = creationTime;
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.pattern.richdomainmodel;
public class Player {
private int points ;
final String name;
public Player(String name) {
this(name, 0);
}
private Player(String name, int points) {
this.name = name;
this.points = 0;
}
public void gainPoint() {
points++;
}
public boolean hasScoreBiggerThan(Score score) {
return this.points > score.points();
}
public int pointsDifference(Player other) {
return points - other.points;
}
public String name() {
return name;
}
public String score() {
return Score.from(points).label();
}
}

View File

@ -0,0 +1,29 @@
package com.baeldung.pattern.richdomainmodel;
import java.util.Arrays;
public enum Score {
LOVE(0, "Love"), FIFTEEN(1, "Fifteen"), THIRTY(2, "Thirty"), FORTY(3, "Forty");
private final int points;
private final String label;
Score(int points, String label) {
this.points = points;
this.label = label;
}
public static Score from(int value) {
return Arrays.stream(values())
.filter(v -> v.points == value)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("no such element: " + value));
}
public int points() {
return points;
}
public String label() {
return label;
}
}

View File

@ -0,0 +1,71 @@
package com.baeldung.pattern.richdomainmodel;
public class TennisGame {
private final Player server;
private final Player receiver;
public TennisGame(String server, String receiver) {
this.server = new Player(server);
this.receiver = new Player(receiver);
}
public void wonPoint(String playerName) {
if(server.name().equals(playerName)) {
server.gainPoint();
} else {
receiver.gainPoint();
}
}
public String getScore() {
if (gameContinues()) {
return getGameScore();
}
return "Win for " + leadingPlayer().name();
}
private String getGameScore() {
if (isScoreEqual()) {
return getEqualScore();
}
if (isAdvantage()) {
return "Advantage " + leadingPlayer().name();
}
return getSimpleScore();
}
private boolean isScoreEqual() {
return server.pointsDifference(receiver) == 0;
}
private boolean isAdvantage() {
return leadingPlayer().hasScoreBiggerThan(Score.FORTY)
&& Math.abs(server.pointsDifference(receiver)) == 1;
}
private boolean isGameFinished() {
return leadingPlayer().hasScoreBiggerThan(Score.FORTY)
&& Math.abs(server.pointsDifference(receiver)) >= 2;
}
private Player leadingPlayer() {
if (server.pointsDifference(receiver) > 0) {
return server;
}
return receiver;
}
private boolean gameContinues() {
return !isGameFinished();
}
private String getSimpleScore() {
return String.format("%s-%s", server.score(), receiver.score());
}
private String getEqualScore() {
if (server.hasScoreBiggerThan(Score.THIRTY)) {
return "Deuce";
}
return String.format("%s-All", server.score());
}
}

View File

@ -0,0 +1,2 @@
server.port=8080
server.error.include-message=always

View File

@ -0,0 +1,50 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import org.mockito.ArgumentCaptor;
import org.junit.jupiter.api.Test;
import org.springframework.web.server.ResponseStatusException;
class UserResponseFormatterUnitTest {
UserResponseFormatter userResponseFormatter = new UserResponseFormatter();
UserRegisterDsGateway userDsGateway = mock(UserRegisterDsGateway.class);
UserPresenter userPresenter = mock(UserPresenter.class);
UserFactory userFactory = mock(UserFactory.class);
UserInputBoundary userInputBoundary = new UserRegisterInteractor(userDsGateway, userPresenter, userFactory);
ArgumentCaptor<String> userRequestModelArgumentCaptor = ArgumentCaptor.forClass(String.class);
@Test
void givenDateAnd3HourTime_whenPrepareSuccessView_thenReturnOnly3HourTime() {
UserResponseModel modelResponse = new UserResponseModel("baeldung", "2020-12-20T03:00:00.000");
UserResponseModel formattedResponse = userResponseFormatter.prepareSuccessView(modelResponse);
assertThat(formattedResponse.getCreationTime()).isEqualTo("03:00:00");
}
@Test
void whenPrepareFailView_thenThrowHttpConflictException() {
assertThatThrownBy(() -> userResponseFormatter.prepareFailView("Invalid password")).isInstanceOf(ResponseStatusException.class);
}
@Test
void whenCreateUser_thenSuccess() {
UserRequestModel userRequestModel = new UserRequestModel("baeldung", "123456");
when(userFactory.create(anyString(), anyString())).thenReturn(new CommonUser("baeldung", "123456"));
userInputBoundary.create(userRequestModel);
verify(userDsGateway).existsByName(userRequestModelArgumentCaptor.capture());
String name = userRequestModel.getName();
assertEquals("baeldung", name);
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.pattern.cleanarchitecture.usercreation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import org.junit.jupiter.api.Test;
class UserUnitTest {
UserRegisterDsGateway userDsGateway = mock(UserRegisterDsGateway.class);
UserPresenter userPresenter = mock(UserPresenter.class);
UserFactory userFactory = mock(UserFactory.class);
UserInputBoundary interactor = new UserRegisterInteractor(userDsGateway, userPresenter, userFactory);
@Test
void given123Password_whenPasswordIsNotValid_thenIsFalse() {
User user = new CommonUser("Baeldung", "123");
assertThat(user.passwordIsValid()).isFalse();
}
@Test
void givenBaeldungUserAnd123456Password_whenCreate_thenSaveItAndPrepareSuccessView() {
User user = new CommonUser("baeldung", "123456");
UserRequestModel userRequestModel = new UserRequestModel(user.getName(), user.getPassword());
when(userFactory.create(anyString(), anyString())).thenReturn(new CommonUser(user.getName(), user.getPassword()));
interactor.create(userRequestModel);
verify(userDsGateway, times(1)).save(any(UserDsRequestModel.class));
verify(userPresenter, times(1)).prepareSuccessView(any(UserResponseModel.class));
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.pattern.richdomainmodel;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class RichDomainModelUnitTest {
@Test
public void givenATennisGame_whenReceiverWinsThreePoints_thenScoreIsFortyLove() {
TennisGame game = new TennisGame("server", "receiver");
game.wonPoint("server");
game.wonPoint("server");
game.wonPoint("server");
assertThat(game.getScore())
.isEqualTo("Forty-Love");
}
@Test
public void givenATennisGame_whenEachPlayerWonTwoPoints_thenScoreIsThirtyAll() {
TennisGame game = new TennisGame("server", "receiver");
game.wonPoint("server");
game.wonPoint("server");
game.wonPoint("receiver");
game.wonPoint("receiver");
assertThat(game.getScore())
.isEqualTo("Thirty-All");
}
}

View File

@ -0,0 +1,2 @@
- [Coupling in Java](https://www.baeldung.com/java-coupling-classes-tight-loose)

View File

@ -0,0 +1,14 @@
<?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>
<artifactId>coupling</artifactId>
<parent>
<artifactId>patterns-modules</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
</project>

View File

@ -0,0 +1,14 @@
package com.baeldung.loose;
import java.io.File;
import java.util.List;
public class CSVExport implements ExportMetadata {
@Override
public File export(List<Object> metadata) {
System.out.println("Exporting data...");
// Export Metadata
File outputCSV = null;
return outputCSV;
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.loose;
import java.io.File;
import java.util.List;
public interface ExportMetadata {
File export(List<Object> metadata);
}

View File

@ -0,0 +1,7 @@
package com.baeldung.loose;
import java.util.List;
public interface FetchMetadata {
List<Object> fetchMetadata();
}

View File

@ -0,0 +1,12 @@
package com.baeldung.loose;
import java.util.ArrayList;
import java.util.List;
public class JSONFetch implements FetchMetadata{
@Override
public List<Object> fetchMetadata() {
System.out.println("Fetching some json data");
return new ArrayList<>();
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.loose;
import java.util.List;
public class MetadataCollector {
private final FetchMetadata fetchMetadata;
private final ExportMetadata exportMetadata;
public MetadataCollector(FetchMetadata fetchMetadata, ExportMetadata exportMetadata) {
this.fetchMetadata = fetchMetadata;
this.exportMetadata = exportMetadata;
}
public void collectMetadata() {
List<Object> metadata = fetchMetadata.fetchMetadata();
exportMetadata.export(metadata);
}
public FetchMetadata getFetchMetadata() {
return fetchMetadata;
}
public ExportMetadata getExportMetadata() {
return exportMetadata;
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.loose;
import java.io.File;
import java.util.List;
public class PDFExport implements ExportMetadata {
@Override
public File export(List<Object> metadata) {
System.out.println("PDF Export");
// Some logic
File outputPDF = null;
return outputPDF;
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.loose;
import java.util.ArrayList;
import java.util.List;
public class XMLFetch implements FetchMetadata {
@Override
public List<Object> fetchMetadata() {
List<Object> metadata = new ArrayList<>();
// Do some stuff
return metadata;
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.tight;
import java.io.File;
import java.util.List;
public class CSVExport {
public File export(List<Object> metadata) {
System.out.println("Exporting data...");
// Export Metadata
File outputCSV = null;
return outputCSV;
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.tight;
import java.util.ArrayList;
import java.util.List;
public class JSONFetch {
public List<Object> fetchMetadata() {
List<Object> metadata = new ArrayList<>();
// Do some stuff
return metadata;
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.tight;
import java.util.List;
public class MetadataCollector {
private XMLFetch xmlFetch = new XMLFetch();
private JSONFetch jsonFetch = new JSONFetch();
private CSVExport csvExport = new CSVExport();
private PDFExport pdfExport = new PDFExport();
public void collectMetadata() {
List<Object> metadata = xmlFetch.fetchMetadata();
csvExport.export(metadata);
}
public void collectMetadata(int inputType, int outputType) {
if (outputType == 1) {
List<Object> metadata = null;
if (inputType == 1) {
metadata = xmlFetch.fetchMetadata();
} else {
metadata = jsonFetch.fetchMetadata();
}
csvExport.export(metadata);
} else {
List<Object> metadata = null;
if (inputType == 1) {
metadata = xmlFetch.fetchMetadata();
} else {
metadata = jsonFetch.fetchMetadata();
}
pdfExport.export(metadata);
}
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.tight;
import java.io.File;
import java.util.List;
public class PDFExport {
public File export(List<Object> metadata) {
System.out.println("Exporting data...");
// Export Metadata
File outputPDF = null;
return outputPDF;
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.tight;
import java.util.ArrayList;
import java.util.List;
public class XMLFetch {
public List<Object> fetchMetadata() {
List<Object> metadata = new ArrayList<>();
// Do some stuff
return metadata;
}
}

View File

@ -0,0 +1,38 @@
package com.baeldung.loose;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class LooselyCouplingUnitTest {
@Test
public void givenMetadataCollector_thenCollectMetadataXMLAndExportCSV() {
FetchMetadata metadata = new XMLFetch();
ExportMetadata exportMetadata = new CSVExport();
MetadataCollector collector = new MetadataCollector(metadata, exportMetadata);
collector.collectMetadata();
assertTrue(collector.getExportMetadata() instanceof CSVExport);
assertTrue(collector.getFetchMetadata() instanceof XMLFetch);
}
@Test
public void givenMetadataCollector_thenCollectMetadataUsingJSONAndExportPDF() {
FetchMetadata metadata = new JSONFetch();
ExportMetadata exportMetadata = new PDFExport();
MetadataCollector collector = new MetadataCollector(metadata, exportMetadata);
collector.collectMetadata();
assertTrue(collector.getExportMetadata() instanceof PDFExport);
assertTrue(collector.getFetchMetadata() instanceof JSONFetch);
}
@Test
public void givenMetadataCollector_thenCollectMetadataUsingXMLAndExportPDF() {
FetchMetadata metadata = new XMLFetch();
ExportMetadata exportMetadata = new PDFExport();
MetadataCollector collector = new MetadataCollector(metadata, exportMetadata);
collector.collectMetadata();
assertTrue(collector.getExportMetadata() instanceof PDFExport);
assertTrue(collector.getFetchMetadata() instanceof XMLFetch);
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.tight;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import org.junit.jupiter.api.Test;
class TightlyCouplingUnitTest {
@Test
public void givenMetadataCollector_thenCollectMetadata() {
MetadataCollector collector = mock(MetadataCollector.class);
doNothing().when(collector)
.collectMetadata();
}
@Test
public void givenMetadataCollectorWithDifferentInput_thenCollectMetadata() {
MetadataCollector collector = new MetadataCollector();
collector.collectMetadata(1, 1);
}
}

View File

@ -0,0 +1,5 @@
This module contains articles about composing together CQRS and Event Sourcing
## Relevant Articles
- [CQRS and Event Sourcing in Java](https://www.baeldung.com/cqrs-event-sourcing-java)

View File

@ -0,0 +1,23 @@
<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>
<artifactId>cqrs-es</artifactId>
<version>1.0-SNAPSHOT</version>
<name>cqrs-es</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>patterns-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package com.baeldung.patterns.cqrs.aggregates;
import com.baeldung.patterns.cqrs.commands.CreateUserCommand;
import com.baeldung.patterns.cqrs.commands.UpdateUserCommand;
import com.baeldung.patterns.cqrs.repository.UserWriteRepository;
import com.baeldung.patterns.domain.User;
public class UserAggregate {
private UserWriteRepository writeRepository;
public UserAggregate(UserWriteRepository repository) {
this.writeRepository = repository;
}
public User handleCreateUserCommand(CreateUserCommand command) {
User user = new User(command.getUserId(), command.getFirstName(), command.getLastName());
writeRepository.addUser(user.getUserid(), user);
return user;
}
public User handleUpdateUserCommand(UpdateUserCommand command) {
User user = writeRepository.getUser(command.getUserId());
user.setAddresses(command.getAddresses());
user.setContacts(command.getContacts());
writeRepository.addUser(user.getUserid(), user);
return user;
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.patterns.cqrs.commands;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CreateUserCommand {
private String userId;
private String firstName;
private String lastName;
}

View File

@ -0,0 +1,20 @@
package com.baeldung.patterns.cqrs.commands;
import java.util.HashSet;
import java.util.Set;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class UpdateUserCommand {
private String userId;
private Set<Address> addresses = new HashSet<>();
private Set<Contact> contacts = new HashSet<>();
}

View File

@ -0,0 +1,37 @@
package com.baeldung.patterns.cqrs.projections;
import java.util.Set;
import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery;
import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery;
import com.baeldung.patterns.cqrs.repository.UserReadRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.UserAddress;
import com.baeldung.patterns.domain.UserContact;
public class UserProjection {
private UserReadRepository repository;
public UserProjection(UserReadRepository repository) {
this.repository = repository;
}
public Set<Contact> handle(ContactByTypeQuery query) throws Exception {
UserContact userContact = repository.getUserContact(query.getUserId());
if (userContact == null)
throw new Exception("User does not exist.");
return userContact.getContactByType()
.get(query.getContactType());
}
public Set<Address> handle(AddressByRegionQuery query) throws Exception {
UserAddress userAddress = repository.getUserAddress(query.getUserId());
if (userAddress == null)
throw new Exception("User does not exist.");
return userAddress.getAddressByRegion()
.get(query.getState());
}
}

View File

@ -0,0 +1,49 @@
package com.baeldung.patterns.cqrs.projectors;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.baeldung.patterns.cqrs.repository.UserReadRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
import com.baeldung.patterns.domain.UserAddress;
import com.baeldung.patterns.domain.UserContact;
public class UserProjector {
UserReadRepository readRepository = new UserReadRepository();
public UserProjector(UserReadRepository readRepository) {
this.readRepository = readRepository;
}
public void project(User user) {
UserContact userContact = Optional.ofNullable(readRepository.getUserContact(user.getUserid()))
.orElse(new UserContact());
Map<String, Set<Contact>> contactByType = new HashMap<>();
for (Contact contact : user.getContacts()) {
Set<Contact> contacts = Optional.ofNullable(contactByType.get(contact.getType()))
.orElse(new HashSet<>());
contacts.add(contact);
contactByType.put(contact.getType(), contacts);
}
userContact.setContactByType(contactByType);
readRepository.addUserContact(user.getUserid(), userContact);
UserAddress userAddress = Optional.ofNullable(readRepository.getUserAddress(user.getUserid()))
.orElse(new UserAddress());
Map<String, Set<Address>> addressByRegion = new HashMap<>();
for (Address address : user.getAddresses()) {
Set<Address> addresses = Optional.ofNullable(addressByRegion.get(address.getState()))
.orElse(new HashSet<>());
addresses.add(address);
addressByRegion.put(address.getState(), addresses);
}
userAddress.setAddressByRegion(addressByRegion);
readRepository.addUserAddress(user.getUserid(), userAddress);
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.patterns.cqrs.queries;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class AddressByRegionQuery {
private String userId;
private String state;
}

View File

@ -0,0 +1,12 @@
package com.baeldung.patterns.cqrs.queries;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ContactByTypeQuery {
private String userId;
private String contactType;
}

View File

@ -0,0 +1,31 @@
package com.baeldung.patterns.cqrs.repository;
import java.util.HashMap;
import java.util.Map;
import com.baeldung.patterns.domain.UserAddress;
import com.baeldung.patterns.domain.UserContact;
public class UserReadRepository {
private Map<String, UserAddress> userAddress = new HashMap<>();
private Map<String, UserContact> userContact = new HashMap<>();
public void addUserAddress(String id, UserAddress user) {
userAddress.put(id, user);
}
public UserAddress getUserAddress(String id) {
return userAddress.get(id);
}
public void addUserContact(String id, UserContact user) {
userContact.put(id, user);
}
public UserContact getUserContact(String id) {
return userContact.get(id);
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.patterns.cqrs.repository;
import java.util.HashMap;
import java.util.Map;
import com.baeldung.patterns.domain.User;
public class UserWriteRepository {
private Map<String, User> store = new HashMap<>();
public void addUser(String id, User user) {
store.put(id, user);
}
public User getUser(String id) {
return store.get(id);
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.patterns.crud.repository;
import java.util.HashMap;
import java.util.Map;
import com.baeldung.patterns.domain.User;
public class UserRepository {
private Map<String, User> store = new HashMap<>();
public void addUser(String id, User user) {
store.put(id, user);
}
public User getUser(String id) {
return store.get(id);
}
}

View File

@ -0,0 +1,55 @@
package com.baeldung.patterns.crud.service;
import java.util.Set;
import java.util.stream.Collectors;
import com.baeldung.patterns.crud.repository.UserRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public void createUser(String userId, String firstName, String lastName) {
User user = new User(userId, firstName, lastName);
repository.addUser(userId, user);
}
public void updateUser(String userId, Set<Contact> contacts, Set<Address> addresses) throws Exception {
User user = repository.getUser(userId);
if (user == null)
throw new Exception("User does not exist.");
user.setContacts(contacts);
user.setAddresses(addresses);
repository.addUser(userId, user);
}
public Set<Contact> getContactByType(String userId, String contactType) throws Exception {
User user = repository.getUser(userId);
if (user == null)
throw new Exception("User does not exit.");
Set<Contact> contacts = user.getContacts();
return contacts.stream()
.filter(c -> c.getType()
.equals(contactType))
.collect(Collectors.toSet());
}
public Set<Address> getAddressByRegion(String userId, String state) throws Exception {
User user = repository.getUser(userId);
if (user == null)
throw new Exception("User does not exist.");
Set<Address> addresses = user.getAddresses();
return addresses.stream()
.filter(a -> a.getState()
.equals(state))
.collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.patterns.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Address {
private String city;
private String state;
private String postcode;
}

View File

@ -0,0 +1,13 @@
package com.baeldung.patterns.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Contact {
private String type;
private String detail;
}

View File

@ -0,0 +1,22 @@
package com.baeldung.patterns.domain;
import java.util.HashSet;
import java.util.Set;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class User {
@NonNull
private String userid;
@NonNull
private String firstname;
@NonNull
private String lastname;
private Set<Contact> contacts = new HashSet<>();
private Set<Address> addresses = new HashSet<>();
}

View File

@ -0,0 +1,14 @@
package com.baeldung.patterns.domain;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import lombok.Data;
@Data
public class UserAddress {
private Map<String, Set<Address>> addressByRegion = new HashMap<>();
}

View File

@ -0,0 +1,14 @@
package com.baeldung.patterns.domain;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import lombok.Data;
@Data
public class UserContact {
private Map<String, Set<Contact>> contactByType = new HashMap<>();
}

View File

@ -0,0 +1,15 @@
package com.baeldung.patterns.es.events;
import java.util.Date;
import java.util.UUID;
import lombok.ToString;
@ToString
public abstract class Event {
public final UUID id = UUID.randomUUID();
public final Date created = new Date();
}

View File

@ -0,0 +1,16 @@
package com.baeldung.patterns.es.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UserAddressAddedEvent extends Event {
private String city;
private String state;
private String postCode;
}

View File

@ -0,0 +1,16 @@
package com.baeldung.patterns.es.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UserAddressRemovedEvent extends Event {
private String city;
private String state;
private String postCode;
}

View File

@ -0,0 +1,15 @@
package com.baeldung.patterns.es.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UserContactAddedEvent extends Event {
private String contactType;
private String contactDetails;
}

View File

@ -0,0 +1,15 @@
package com.baeldung.patterns.es.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UserContactRemovedEvent extends Event {
private String contactType;
private String contactDetails;
}

View File

@ -0,0 +1,16 @@
package com.baeldung.patterns.es.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UserCreatedEvent extends Event {
private String userId;
private String firstName;
private String lastName;
}

View File

@ -0,0 +1,29 @@
package com.baeldung.patterns.es.repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.baeldung.patterns.es.events.Event;
public class EventStore {
private Map<String, List<Event>> store = new HashMap<>();
public void addEvent(String id, Event event) {
List<Event> events = store.get(id);
if (events == null) {
events = new ArrayList<Event>();
events.add(event);
store.put(id, events);
} else {
events.add(event);
}
}
public List<Event> getEvents(String id) {
return store.get(id);
}
}

View File

@ -0,0 +1,72 @@
package com.baeldung.patterns.es.service;
import java.util.Set;
import java.util.stream.Collectors;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
import com.baeldung.patterns.es.events.UserAddressAddedEvent;
import com.baeldung.patterns.es.events.UserAddressRemovedEvent;
import com.baeldung.patterns.es.events.UserContactAddedEvent;
import com.baeldung.patterns.es.events.UserContactRemovedEvent;
import com.baeldung.patterns.es.events.UserCreatedEvent;
import com.baeldung.patterns.es.repository.EventStore;
public class UserService {
private EventStore repository;
public UserService(EventStore repository) {
this.repository = repository;
}
public void createUser(String userId, String firstName, String lastName) {
repository.addEvent(userId, new UserCreatedEvent(userId, firstName, lastName));
}
public void updateUser(String userId, Set<Contact> contacts, Set<Address> addresses) throws Exception {
User user = UserUtility.recreateUserState(repository, userId);
if (user == null)
throw new Exception("User does not exist.");
user.getContacts()
.stream()
.filter(c -> !contacts.contains(c))
.forEach(c -> repository.addEvent(userId, new UserContactRemovedEvent(c.getType(), c.getDetail())));
contacts.stream()
.filter(c -> !user.getContacts()
.contains(c))
.forEach(c -> repository.addEvent(userId, new UserContactAddedEvent(c.getType(), c.getDetail())));
user.getAddresses()
.stream()
.filter(a -> !addresses.contains(a))
.forEach(a -> repository.addEvent(userId, new UserAddressRemovedEvent(a.getCity(), a.getState(), a.getPostcode())));
addresses.stream()
.filter(a -> !user.getAddresses()
.contains(a))
.forEach(a -> repository.addEvent(userId, new UserAddressAddedEvent(a.getCity(), a.getState(), a.getPostcode())));
}
public Set<Contact> getContactByType(String userId, String contactType) throws Exception {
User user = UserUtility.recreateUserState(repository, userId);
if (user == null)
throw new Exception("User does not exist.");
return user.getContacts()
.stream()
.filter(c -> c.getType()
.equals(contactType))
.collect(Collectors.toSet());
}
public Set<Address> getAddressByRegion(String userId, String state) throws Exception {
User user = UserUtility.recreateUserState(repository, userId);
if (user == null)
throw new Exception("User does not exist.");
return user.getAddresses()
.stream()
.filter(a -> a.getState()
.equals(state))
.collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,61 @@
package com.baeldung.patterns.es.service;
import java.util.List;
import java.util.UUID;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
import com.baeldung.patterns.es.events.Event;
import com.baeldung.patterns.es.events.UserAddressAddedEvent;
import com.baeldung.patterns.es.events.UserAddressRemovedEvent;
import com.baeldung.patterns.es.events.UserContactAddedEvent;
import com.baeldung.patterns.es.events.UserContactRemovedEvent;
import com.baeldung.patterns.es.events.UserCreatedEvent;
import com.baeldung.patterns.es.repository.EventStore;
public class UserUtility {
public static User recreateUserState(EventStore store, String userId) {
User user = null;
List<Event> events = store.getEvents(userId);
for (Event event : events) {
if (event instanceof UserCreatedEvent) {
UserCreatedEvent e = (UserCreatedEvent) event;
user = new User(e.getUserId(), e.getFirstName(), e.getLastName());
}
if (event instanceof UserAddressAddedEvent) {
UserAddressAddedEvent e = (UserAddressAddedEvent) event;
Address address = new Address(e.getCity(), e.getState(), e.getPostCode());
if (user != null)
user.getAddresses()
.add(address);
}
if (event instanceof UserAddressRemovedEvent) {
UserAddressRemovedEvent e = (UserAddressRemovedEvent) event;
Address address = new Address(e.getCity(), e.getState(), e.getPostCode());
if (user != null)
user.getAddresses()
.remove(address);
}
if (event instanceof UserContactAddedEvent) {
UserContactAddedEvent e = (UserContactAddedEvent) event;
Contact contact = new Contact(e.getContactType(), e.getContactDetails());
if (user != null)
user.getContacts()
.add(contact);
}
if (event instanceof UserContactRemovedEvent) {
UserContactRemovedEvent e = (UserContactRemovedEvent) event;
Contact contact = new Contact(e.getContactType(), e.getContactDetails());
if (user != null)
user.getContacts()
.remove(contact);
}
}
return user;
}
}

View File

@ -0,0 +1,87 @@
package com.baeldung.patterns.escqrs.aggregates;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.baeldung.patterns.cqrs.commands.CreateUserCommand;
import com.baeldung.patterns.cqrs.commands.UpdateUserCommand;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
import com.baeldung.patterns.es.events.Event;
import com.baeldung.patterns.es.events.UserAddressAddedEvent;
import com.baeldung.patterns.es.events.UserAddressRemovedEvent;
import com.baeldung.patterns.es.events.UserContactAddedEvent;
import com.baeldung.patterns.es.events.UserContactRemovedEvent;
import com.baeldung.patterns.es.events.UserCreatedEvent;
import com.baeldung.patterns.es.repository.EventStore;
import com.baeldung.patterns.es.service.UserUtility;
public class UserAggregate {
private EventStore writeRepository;
public UserAggregate(EventStore repository) {
this.writeRepository = repository;
}
public List<Event> handleCreateUserCommand(CreateUserCommand command) {
UserCreatedEvent event = new UserCreatedEvent(command.getUserId(), command.getFirstName(), command.getLastName());
writeRepository.addEvent(command.getUserId(), event);
return Arrays.asList(event);
}
public List<Event> handleUpdateUserCommand(UpdateUserCommand command) {
User user = UserUtility.recreateUserState(writeRepository, command.getUserId());
List<Event> events = new ArrayList<>();
List<Contact> contactsToRemove = user.getContacts()
.stream()
.filter(c -> !command.getContacts()
.contains(c))
.collect(Collectors.toList());
for (Contact contact : contactsToRemove) {
UserContactRemovedEvent contactRemovedEvent = new UserContactRemovedEvent(contact.getType(), contact.getDetail());
events.add(contactRemovedEvent);
writeRepository.addEvent(command.getUserId(), contactRemovedEvent);
}
List<Contact> contactsToAdd = command.getContacts()
.stream()
.filter(c -> !user.getContacts()
.contains(c))
.collect(Collectors.toList());
for (Contact contact : contactsToAdd) {
UserContactAddedEvent contactAddedEvent = new UserContactAddedEvent(contact.getType(), contact.getDetail());
events.add(contactAddedEvent);
writeRepository.addEvent(command.getUserId(), contactAddedEvent);
}
List<Address> addressesToRemove = user.getAddresses()
.stream()
.filter(a -> !command.getAddresses()
.contains(a))
.collect(Collectors.toList());
for (Address address : addressesToRemove) {
UserAddressRemovedEvent addressRemovedEvent = new UserAddressRemovedEvent(address.getCity(), address.getState(), address.getPostcode());
events.add(addressRemovedEvent);
writeRepository.addEvent(command.getUserId(), addressRemovedEvent);
}
List<Address> addressesToAdd = command.getAddresses()
.stream()
.filter(a -> !user.getAddresses()
.contains(a))
.collect(Collectors.toList());
for (Address address : addressesToAdd) {
UserAddressAddedEvent addressAddedEvent = new UserAddressAddedEvent(address.getCity(), address.getState(), address.getPostcode());
events.add(addressAddedEvent);
writeRepository.addEvent(command.getUserId(), addressAddedEvent);
}
return events;
}
}

View File

@ -0,0 +1,91 @@
package com.baeldung.patterns.escqrs.projectors;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import com.baeldung.patterns.cqrs.repository.UserReadRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.UserAddress;
import com.baeldung.patterns.domain.UserContact;
import com.baeldung.patterns.es.events.Event;
import com.baeldung.patterns.es.events.UserAddressAddedEvent;
import com.baeldung.patterns.es.events.UserAddressRemovedEvent;
import com.baeldung.patterns.es.events.UserContactAddedEvent;
import com.baeldung.patterns.es.events.UserContactRemovedEvent;
public class UserProjector {
UserReadRepository readRepository = new UserReadRepository();
public UserProjector(UserReadRepository readRepository) {
this.readRepository = readRepository;
}
public void project(String userId, List<Event> events) {
for (Event event : events) {
if (event instanceof UserAddressAddedEvent)
apply(userId, (UserAddressAddedEvent) event);
if (event instanceof UserAddressRemovedEvent)
apply(userId, (UserAddressRemovedEvent) event);
if (event instanceof UserContactAddedEvent)
apply(userId, (UserContactAddedEvent) event);
if (event instanceof UserContactRemovedEvent)
apply(userId, (UserContactRemovedEvent) event);
}
}
public void apply(String userId, UserAddressAddedEvent event) {
Address address = new Address(event.getCity(), event.getState(), event.getPostCode());
UserAddress userAddress = Optional.ofNullable(readRepository.getUserAddress(userId))
.orElse(new UserAddress());
Set<Address> addresses = Optional.ofNullable(userAddress.getAddressByRegion()
.get(address.getState()))
.orElse(new HashSet<>());
addresses.add(address);
userAddress.getAddressByRegion()
.put(address.getState(), addresses);
readRepository.addUserAddress(userId, userAddress);
}
public void apply(String userId, UserAddressRemovedEvent event) {
Address address = new Address(event.getCity(), event.getState(), event.getPostCode());
UserAddress userAddress = readRepository.getUserAddress(userId);
if (userAddress != null) {
Set<Address> addresses = userAddress.getAddressByRegion()
.get(address.getState());
if (addresses != null)
addresses.remove(address);
readRepository.addUserAddress(userId, userAddress);
}
}
public void apply(String userId, UserContactAddedEvent event) {
Contact contact = new Contact(event.getContactType(), event.getContactDetails());
UserContact userContact = Optional.ofNullable(readRepository.getUserContact(userId))
.orElse(new UserContact());
Set<Contact> contacts = Optional.ofNullable(userContact.getContactByType()
.get(contact.getType()))
.orElse(new HashSet<>());
contacts.add(contact);
userContact.getContactByType()
.put(contact.getType(), contacts);
readRepository.addUserContact(userId, userContact);
}
public void apply(String userId, UserContactRemovedEvent event) {
Contact contact = new Contact(event.getContactType(), event.getContactDetails());
UserContact userContact = readRepository.getUserContact(userId);
if (userContact != null) {
Set<Contact> contacts = userContact.getContactByType()
.get(contact.getType());
if (contacts != null)
contacts.remove(contact);
readRepository.addUserContact(userId, userContact);
}
}
}

View File

@ -0,0 +1,74 @@
package com.baeldung.patterns.cqrs;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import com.baeldung.patterns.cqrs.aggregates.UserAggregate;
import com.baeldung.patterns.cqrs.commands.CreateUserCommand;
import com.baeldung.patterns.cqrs.commands.UpdateUserCommand;
import com.baeldung.patterns.cqrs.projections.UserProjection;
import com.baeldung.patterns.cqrs.projectors.UserProjector;
import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery;
import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery;
import com.baeldung.patterns.cqrs.repository.UserReadRepository;
import com.baeldung.patterns.cqrs.repository.UserWriteRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.domain.User;
public class ApplicationUnitTest {
private UserWriteRepository writeRepository;
private UserReadRepository readRepository;
private UserProjector projector;
private UserAggregate userAggregate;
private UserProjection userProjection;
@Before
public void setUp() {
writeRepository = new UserWriteRepository();
readRepository = new UserReadRepository();
projector = new UserProjector(readRepository);
userAggregate = new UserAggregate(writeRepository);
userProjection = new UserProjection(readRepository);
}
@Test
public void givenCQRSApplication_whenCommandRun_thenQueryShouldReturnResult() throws Exception {
String userId = UUID.randomUUID()
.toString();
User user = null;
CreateUserCommand createUserCommand = new CreateUserCommand(userId, "Tom", "Sawyer");
user = userAggregate.handleCreateUserCommand(createUserCommand);
projector.project(user);
UpdateUserCommand updateUserCommand = new UpdateUserCommand(user.getUserid(), Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"))
.collect(Collectors.toSet()),
Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"))
.collect(Collectors.toSet()));
user = userAggregate.handleUpdateUserCommand(updateUserCommand);
projector.project(user);
updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()),
Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()));
user = userAggregate.handleUpdateUserCommand(updateUserCommand);
projector.project(user);
ContactByTypeQuery contactByTypeQuery = new ContactByTypeQuery(userId, "EMAIL");
assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"))
.collect(Collectors.toSet()), userProjection.handle(contactByTypeQuery));
AddressByRegionQuery addressByRegionQuery = new AddressByRegionQuery(userId, "NY");
assertEquals(Stream.of(new Address("New York", "NY", "10001"))
.collect(Collectors.toSet()), userProjection.handle(addressByRegionQuery));
}
}

View File

@ -0,0 +1,48 @@
package com.baeldung.patterns.crud;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import com.baeldung.patterns.crud.repository.UserRepository;
import com.baeldung.patterns.crud.service.UserService;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
public class ApplicationUnitTest {
private UserRepository repository;
@Before
public void setUp() {
repository = new UserRepository();
}
@Test
public void givenCRUDApplication_whenDataCreated_thenDataCanBeFetched() throws Exception {
UserService service = new UserService(repository);
String userId = UUID.randomUUID()
.toString();
service.createUser(userId, "Tom", "Sawyer");
service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()),
Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()));
service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()),
Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()));
assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"))
.collect(Collectors.toSet()), service.getContactByType(userId, "EMAIL"));
assertEquals(Stream.of(new Address("New York", "NY", "10001"))
.collect(Collectors.toSet()), service.getAddressByRegion(userId, "NY"));
}
}

View File

@ -0,0 +1,50 @@
package com.baeldung.patterns.es;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.es.repository.EventStore;
import com.baeldung.patterns.es.service.UserService;
public class ApplicationUnitTest {
private EventStore repository;
private UserService service;
@Before
public void setUp() {
repository = new EventStore();
service = new UserService(repository);
}
@Test
public void givenCRUDApplication_whenDataCreated_thenDataCanBeFetched() throws Exception {
String userId = UUID.randomUUID()
.toString();
service.createUser(userId, "Tom", "Sawyer");
service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()),
Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()));
service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()),
Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()));
assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"))
.collect(Collectors.toSet()), service.getContactByType(userId, "EMAIL"));
assertEquals(Stream.of(new Address("New York", "NY", "10001"))
.collect(Collectors.toSet()), service.getAddressByRegion(userId, "NY"));
}
}

View File

@ -0,0 +1,76 @@
package com.baeldung.patterns.escqrs;
import static org.junit.Assert.assertEquals;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import com.baeldung.patterns.cqrs.commands.CreateUserCommand;
import com.baeldung.patterns.cqrs.commands.UpdateUserCommand;
import com.baeldung.patterns.cqrs.projections.UserProjection;
import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery;
import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery;
import com.baeldung.patterns.cqrs.repository.UserReadRepository;
import com.baeldung.patterns.domain.Address;
import com.baeldung.patterns.domain.Contact;
import com.baeldung.patterns.es.events.Event;
import com.baeldung.patterns.es.repository.EventStore;
import com.baeldung.patterns.escqrs.aggregates.UserAggregate;
import com.baeldung.patterns.escqrs.projectors.UserProjector;
public class ApplicationUnitTest {
private EventStore writeRepository;
private UserReadRepository readRepository;
private UserProjector projector;
private UserAggregate userAggregate;
private UserProjection userProjection;
@Before
public void setUp() {
writeRepository = new EventStore();
readRepository = new UserReadRepository();
projector = new UserProjector(readRepository);
userAggregate = new UserAggregate(writeRepository);
userProjection = new UserProjection(readRepository);
}
@Test
public void givenCQRSApplication_whenCommandRun_thenQueryShouldReturnResult() throws Exception {
String userId = UUID.randomUUID()
.toString();
List<Event> events = null;
CreateUserCommand createUserCommand = new CreateUserCommand(userId, "Kumar", "Chandrakant");
events = userAggregate.handleCreateUserCommand(createUserCommand);
projector.project(userId, events);
UpdateUserCommand updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"))
.collect(Collectors.toSet()),
Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"))
.collect(Collectors.toSet()));
events = userAggregate.handleUpdateUserCommand(updateUserCommand);
projector.project(userId, events);
updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001"))
.collect(Collectors.toSet()),
Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001"))
.collect(Collectors.toSet()));
events = userAggregate.handleUpdateUserCommand(updateUserCommand);
projector.project(userId, events);
ContactByTypeQuery contactByTypeQuery = new ContactByTypeQuery(userId, "EMAIL");
assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"))
.collect(Collectors.toSet()), userProjection.handle(contactByTypeQuery));
AddressByRegionQuery addressByRegionQuery = new AddressByRegionQuery(userId, "NY");
assertEquals(Stream.of(new Address("New York", "NY", "10001"))
.collect(Collectors.toSet()), userProjection.handle(addressByRegionQuery));
}
}

View File

@ -0,0 +1,7 @@
### Relevant Articles:
- [Service Locator Pattern and Java Implementation](https://www.baeldung.com/java-service-locator-pattern)
- [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern)
- [DAO vs Repository Patterns](https://www.baeldung.com/java-dao-vs-repository)
- [Difference Between MVC and MVP Patterns](https://www.baeldung.com/mvc-vs-mvp-pattern)
- [The DTO Pattern (Data Transfer Object)](https://www.baeldung.com/java-dto-pattern)
- [SEDA With Spring Integration and Apache Camel](https://www.baeldung.com/spring-apache-camel-seda-integration)

View File

@ -0,0 +1,77 @@
<?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>
<artifactId>design-patterns-architectural</artifactId>
<version>1.0</version>
<name>design-patterns-architectural</name>
<packaging>jar</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>patterns-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
<version>${spring-boot-starter-integration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<version>${spring-integration-test.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel-core.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-junit5</artifactId>
<version>${camel-test-junit5.version}</version>
</dependency>
</dependencies>
<properties>
<hibernate-core.version>5.2.16.Final</hibernate-core.version>
<mysql-connector.version>6.0.6</mysql-connector.version>
<spring-boot.version>2.7.5</spring-boot.version>
<rest-assured.version>5.3.0</rest-assured.version>
<spring-boot-starter-integration.version>2.7.5</spring-boot-starter-integration.version>
<spring-integration-test.version>5.5.14</spring-integration-test.version>
<camel-core.version>3.20.4</camel-core.version>
<camel-test-junit5.version>3.14.0</camel-test-junit5.version>
</properties>
</project>

View File

@ -0,0 +1,12 @@
package com.baeldung.dtopattern;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

View File

@ -0,0 +1,28 @@
package com.baeldung.dtopattern.api;
import com.baeldung.dtopattern.domain.Role;
import com.baeldung.dtopattern.domain.User;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
@Component
class Mapper {
public UserDTO toDto(User user) {
String name = user.getName();
List<String> roles = user
.getRoles()
.stream()
.map(Role::getName)
.collect(toList());
return new UserDTO(name, roles);
}
public User toUser(UserCreationDTO userDTO) {
return new User(userDTO.getName(), userDTO.getPassword(), new ArrayList<>());
}
}

View File

@ -0,0 +1,51 @@
package com.baeldung.dtopattern.api;
import com.baeldung.dtopattern.domain.RoleService;
import com.baeldung.dtopattern.domain.User;
import com.baeldung.dtopattern.domain.UserService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static java.util.stream.Collectors.toList;
@RestController
@RequestMapping("/users")
class UserController {
private UserService userService;
private RoleService roleService;
private Mapper mapper;
public UserController(UserService userService, RoleService roleService, Mapper mapper) {
this.userService = userService;
this.roleService = roleService;
this.mapper = mapper;
}
@GetMapping
@ResponseBody
public List<UserDTO> getUsers() {
return userService.getAll()
.stream()
.map(mapper::toDto)
.collect(toList());
}
@PostMapping
@ResponseBody
public UserIdDTO create(@RequestBody UserCreationDTO userDTO) {
User user = mapper.toUser(userDTO);
userDTO.getRoles()
.stream()
.map(role -> roleService.getOrCreate(role))
.forEach(user::addRole);
userService.save(user);
return new UserIdDTO(user.getId());
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.dtopattern.api;
import java.util.List;
public class UserCreationDTO {
private String name;
private String password;
private List<String> roles;
UserCreationDTO() {}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public List<String> getRoles() {
return roles;
}
void setName(String name) {
this.name = name;
}
void setPassword(String password) {
this.password = password;
}
void setRoles(List<String> roles) {
this.roles = roles;
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.dtopattern.api;
import java.util.List;
public class UserDTO {
private String name;
private List<String> roles;
public UserDTO(String name, List<String> roles) {
this.name = name;
this.roles = roles;
}
public String getName() {
return name;
}
public List<String> getRoles() {
return roles;
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.dtopattern.api;
public class UserIdDTO {
private String id;
public UserIdDTO(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,49 @@
package com.baeldung.dtopattern.domain;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
class InMemoryRepository implements UserRepository, RoleRepository {
private Map<String, User> users = new LinkedHashMap<>();
private Map<String, Role> roles = new LinkedHashMap<>();
@Override
public List<User> getAll() {
return new ArrayList<>(users.values());
}
@Override
public void save(User user) {
user.setId(UUID.randomUUID().toString());
users.put(user.getId(), user);
}
@Override
public void save(Role role) {
role.setId(UUID.randomUUID().toString());
roles.put(role.getId(), role);
}
@Override
public Role getRoleById(String id) {
return roles.get(id);
}
@Override
public Role getRoleByName(String name) {
return roles.values()
.stream()
.filter(role -> role.getName().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
}
@Override
public void deleteAll() {
users.clear();
roles.clear();
}
}

View File

@ -0,0 +1,28 @@
package com.baeldung.dtopattern.domain;
import java.util.Objects;
public class Role {
private String id;
private String name;
public Role(String name) {
this.name = Objects.requireNonNull(name);
}
public String getId() {
return id;
}
void setId(String id) {
this.id = Objects.requireNonNull(id);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = Objects.requireNonNull(name);
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.dtopattern.domain;
public interface RoleRepository {
Role getRoleById(String id);
Role getRoleByName(String name);
void save(Role role);
}

View File

@ -0,0 +1,32 @@
package com.baeldung.dtopattern.domain;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
public class RoleService {
private RoleRepository repository;
public RoleService(RoleRepository repository) {
this.repository = repository;
}
public Role getOrCreate(String name) {
Role role = repository.getRoleByName(name);
if (role == null) {
role = new Role(name);
repository.save(role);
}
return role;
}
public void save(Role role) {
Objects.requireNonNull(role);
repository.save(role);
}
}

View File

@ -0,0 +1,80 @@
package com.baeldung.dtopattern.domain;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class User {
private static SecretKeySpec KEY = initKey();
static SecretKeySpec initKey(){
try {
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
return new SecretKeySpec(secretKey.getEncoded(), "AES");
} catch (NoSuchAlgorithmException ex) {
return null;
}
}
private String id;
private String name;
private String password;
private List<Role> roles;
public User(String name, String password, List<Role> roles) {
this.name = Objects.requireNonNull(name);
this.password = this.encrypt(password);
this.roles = Objects.requireNonNull(roles);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void addRole(Role role) {
roles.add(role);
}
public List<Role> getRoles() {
return Collections.unmodifiableList(roles);
}
public String getId() {
return id;
}
void setId(String id) {
this.id = id;
}
String encrypt(String password) {
Objects.requireNonNull(password);
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, KEY);
final byte[] encryptedBytes = cipher.doFinal(password.getBytes(StandardCharsets.UTF_8));
return new String(encryptedBytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
// do nothing
return "";
}
}
}

View File

@ -0,0 +1,9 @@
package com.baeldung.dtopattern.domain;
import java.util.List;
public interface UserRepository {
List<User> getAll();
void save(User user);
void deleteAll();
}

View File

@ -0,0 +1,25 @@
package com.baeldung.dtopattern.domain;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public List<User> getAll() {
return repository.getAll();
}
public void save(User user) {
Objects.requireNonNull(user);
repository.save(user);
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.mvc_mvp.mvc;
public class MvcMainClass {
public static void main(String[] args) {
Product model = retrieveProductFromDatabase();
ProductView view = new ProductView();
model.setView(view);
model.showProduct();
ProductController controller = new ProductController(model);
controller.setName("SmartPhone");
model.showProduct();
}
private static Product retrieveProductFromDatabase() {
Product product = new Product();
product.setName("Mobile");
product.setDescription("New Brand");
product.setPrice(1000.0);
return product;
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.mvc_mvp.mvc;
public class Product {
private String name;
private String description;
private Double price;
private ProductView view;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public ProductView getView() {
return view;
}
public void setView(ProductView view) {
this.view = view;
}
public void showProduct() {
view.printProductDetails(name, description, price);
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.mvc_mvp.mvc;
public class ProductController {
private final Product product;
public ProductController(Product product) {
this.product = product;
}
public String getName() {
return product.getName();
}
public void setName(String name) {
product.setName(name);
}
public String getDescription() {
return product.getDescription();
}
public void setDescription(String description) {
product.setDescription(description);
}
public Double getPrice() {
return product.getPrice();
}
public void setPrice(Double price) {
product.setPrice(price);
}
}

View File

@ -0,0 +1,15 @@
package com.baeldung.mvc_mvp.mvc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProductView {
private static Logger log = LoggerFactory.getLogger(ProductView.class);
public void printProductDetails(String name, String description, Double price) {
log.info("Product details:");
log.info("product Name: " + name);
log.info("product Description: " + description);
log.info("product price: " + price);
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.mvc_mvp.mvp;
public class MvpMainClass {
public static void main(String[] args) {
Product model = retrieveProductFromDatabase();
ProductView view = new ProductView();
ProductPresenter presenter = new ProductPresenter(model, view);
presenter.showProduct();
presenter.setName("SmartPhone");
presenter.showProduct();
}
private static Product retrieveProductFromDatabase() {
Product product = new Product();
product.setName("Mobile");
product.setDescription("New Brand");
product.setPrice(1000.0);
return product;
}
}

View File

@ -0,0 +1,32 @@
package com.baeldung.mvc_mvp.mvp;
public class Product {
private String name;
private String description;
private Double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}

View File

@ -0,0 +1,40 @@
package com.baeldung.mvc_mvp.mvp;
public class ProductPresenter {
private final Product product;
private final ProductView view;
public ProductPresenter(Product product, ProductView view) {
this.product = product;
this.view = view;
}
public String getName() {
return product.getName();
}
public void setName(String name) {
product.setName(name);
}
public String getDescription() {
return product.getDescription();
}
public void setDescription(String description) {
product.setDescription(description);
}
public Double getProductPrice() {
return product.getPrice();
}
public void setPrice(Double price) {
product.setPrice(price);
}
public void showProduct() {
view.printProductDetails(product.getName(), product.getDescription(), product.getPrice());
}
}

View File

@ -0,0 +1,15 @@
package com.baeldung.mvc_mvp.mvp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProductView {
private static Logger log = LoggerFactory.getLogger(ProductView.class);
public void printProductDetails(String name, String description, Double price) {
log.info("Product details:");
log.info("product Name: " + name);
log.info("product Description: " + description);
log.info("product price: " + price);
}
}

Some files were not shown because too many files have changed in this diff Show More