diff --git a/patterns/clean-architecture/pom.xml b/patterns/clean-architecture/pom.xml
new file mode 100644
index 0000000000..6e7de78751
--- /dev/null
+++ b/patterns/clean-architecture/pom.xml
@@ -0,0 +1,87 @@
+
+
+ 4.0.0
+ clean-architecture
+ 1.0
+ clean-architecture
+ Project for clean architecture in java
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+ 1.8
+
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.junit.platform
+ junit-platform-engine
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.junit.platform
+ junit-platform-runner
+ test
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java
new file mode 100644
index 0000000000..ebac2bacf3
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java
@@ -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");
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java
new file mode 100644
index 0000000000..f7ba9dacc0
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java
@@ -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;
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java
new file mode 100644
index 0000000000..a2b851da94
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java
@@ -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);
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java
new file mode 100644
index 0000000000..20751f282a
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java
@@ -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);
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java
new file mode 100644
index 0000000000..8565ed7965
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java
@@ -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 {
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java
new file mode 100644
index 0000000000..aab652f2a1
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java
@@ -0,0 +1,9 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+interface User {
+ boolean passwordIsValid();
+
+ String getName();
+
+ String getPassword();
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java
new file mode 100644
index 0000000000..44112de8a9
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java
@@ -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;
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java
new file mode 100644
index 0000000000..aa0f0b56d1
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java
@@ -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;
+ }
+
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java
new file mode 100644
index 0000000000..1ff29709be
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java
@@ -0,0 +1,5 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+interface UserFactory {
+ User create(String name, String password);
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java
new file mode 100644
index 0000000000..e72c30f13c
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java
@@ -0,0 +1,5 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+public interface UserInputBoundary {
+ UserResponseModel create(UserRequestModel requestModel);
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java
new file mode 100644
index 0000000000..45d202643e
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java
@@ -0,0 +1,7 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+interface UserPresenter {
+ UserResponseModel prepareSuccessView(UserResponseModel user);
+
+ UserResponseModel prepareFailView(String error);
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java
new file mode 100644
index 0000000000..039dc12910
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java
@@ -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);
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java
new file mode 100644
index 0000000000..89c1b7e774
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java
@@ -0,0 +1,7 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+interface UserRegisterDsGateway {
+ boolean existsByName(String identifier);
+
+ void save(UserDsRequestModel requestModel);
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java
new file mode 100644
index 0000000000..5137593dc3
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java
new file mode 100644
index 0000000000..8317665c31
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java
@@ -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;
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java
new file mode 100644
index 0000000000..4842d44e22
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java
@@ -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);
+ }
+}
diff --git a/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java
new file mode 100644
index 0000000000..73a3d8fb10
--- /dev/null
+++ b/patterns/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java
@@ -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;
+ }
+
+}
diff --git a/patterns/clean-architecture/src/main/resources/application.properties b/patterns/clean-architecture/src/main/resources/application.properties
new file mode 100644
index 0000000000..a5a02bb49d
--- /dev/null
+++ b/patterns/clean-architecture/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+server.port=8080
+server.error.include-message=always
\ No newline at end of file
diff --git a/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterTests.java b/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterTests.java
new file mode 100644
index 0000000000..f8ebde5f10
--- /dev/null
+++ b/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterTests.java
@@ -0,0 +1,29 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.web.server.ResponseStatusException;
+
+import com.baeldung.pattern.cleanarchitecture.usercreation.UserResponseFormatter;
+import com.baeldung.pattern.cleanarchitecture.usercreation.UserResponseModel;
+
+class UserResponseFormatterTests {
+
+ UserResponseFormatter userResponseFormatter = new UserResponseFormatter();
+
+ @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);
+ }
+}
diff --git a/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java b/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java
new file mode 100644
index 0000000000..505ea47e3f
--- /dev/null
+++ b/patterns/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.pattern.cleanarchitecture.usercreation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class UserUnitTest {
+
+ @Test
+ void given123Password_whenPasswordIsNotValid_thenIsFalse() {
+ User user = new CommonUser("Baeldung", "123");
+
+ assertThat(user.passwordIsValid()).isFalse();
+ }
+}