Skip to content

Commit 799c782

Browse files
authored
fix(archi): clean archi of the project (#89)
1 parent b57bea2 commit 799c782

54 files changed

Lines changed: 435 additions & 358 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,10 @@ curl -X PUT "http://localhost:8080/random-users/1" \
254254
PostgreSQL
255255
```
256256

257-
### Domain Service Ports
257+
### Service Architecture
258258

259-
- `LocalUserService`: local persistence operations (save, read, delete) backed by PostgreSQL.
260-
- `RemoteUserService`: external user source contract used by use cases.
259+
- `UserService`: domain port for local persistence operations (save, read, delete) backed by PostgreSQL. Implemented by `UserServiceImpl` in the data layer.
260+
- `RemoteUserService`: data-layer interface for external API adapters. Implemented by `DummyUserServiceImpl` and `RandomUserServiceImpl`.
261261

262262
### External Source Adapter
263263

mvnw.cmd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<testcontainers.version>2.0.4</testcontainers.version>
3232
<skipDocker>false</skipDocker>
3333
<sonar.coverage.exclusions>**/*Application.java</sonar.coverage.exclusions>
34-
<sonar.test.exclusions>**/feature/SpringIntegrationTest.java</sonar.test.exclusions>
34+
<sonar.test.exclusions>**/features/SpringIntegrationTest.java</sonar.test.exclusions>
3535
<sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
3636
</properties>
3737

src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,40 @@
22

33
import com.xpeho.spring_boot_java_random_user.data.models.api.dummy.DummyUserResultDTO;
44
import com.xpeho.spring_boot_java_random_user.data.models.api.randomuser.RandomUserResultDTO;
5-
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
5+
import com.xpeho.spring_boot_java_random_user.data.models.database.UserDAO;
66
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
77
import org.springframework.stereotype.Service;
88

99

1010
@Service
1111
public class UserConverter {
1212
// Domain -> DAO
13-
public User toDao(UserEntity entity) {
14-
User user = new User();
15-
user.setId(entity.id());
16-
user.setGender(entity.gender());
17-
user.setFirstname(entity.firstname());
18-
user.setLastname(entity.lastname());
19-
user.setCivility(entity.civility());
20-
user.setEmail(entity.email());
21-
user.setPhone(entity.phone());
22-
user.setPicture(entity.picture());
23-
user.setNationality(entity.nat());
24-
return user;
13+
public UserDAO toDao(UserEntity entity) {
14+
UserDAO userDAO = new UserDAO();
15+
userDAO.setId(entity.id());
16+
userDAO.setGender(entity.gender());
17+
userDAO.setFirstname(entity.firstname());
18+
userDAO.setLastname(entity.lastname());
19+
userDAO.setCivility(entity.civility());
20+
userDAO.setEmail(entity.email());
21+
userDAO.setPhone(entity.phone());
22+
userDAO.setPicture(entity.picture());
23+
userDAO.setNationality(entity.nat());
24+
return userDAO;
2525
}
2626

2727
// DAO -> Domain
28-
public UserEntity toDomain(User user) {
28+
public UserEntity toDomain(UserDAO userDAO) {
2929
return new UserEntity(
30-
user.getId(),
31-
user.getGender(),
32-
user.getFirstname(),
33-
user.getLastname(),
34-
user.getCivility(),
35-
user.getEmail(),
36-
user.getPhone(),
37-
user.getPicture(),
38-
user.getNationality()
30+
userDAO.getId(),
31+
userDAO.getGender(),
32+
userDAO.getFirstname(),
33+
userDAO.getLastname(),
34+
userDAO.getCivility(),
35+
userDAO.getEmail(),
36+
userDAO.getPhone(),
37+
userDAO.getPicture(),
38+
userDAO.getNationality()
3939
);
4040
}
4141

src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java renamed to src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/UserDAO.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@Entity
1111
@Table(name = "users")
12-
public class User {
12+
public class UserDAO {
1313
@Id
1414
@GeneratedValue(strategy = GenerationType.IDENTITY)
1515
@Column(name = "id")
@@ -32,7 +32,7 @@ public class User {
3232
private String nationality;
3333

3434
// Required by JPA
35-
public User() {
35+
public UserDAO() {
3636
// No initialization needed
3737
}
3838

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
package com.xpeho.spring_boot_java_random_user.data.services;
22

33
import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter;
4-
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
4+
import com.xpeho.spring_boot_java_random_user.data.models.database.UserDAO;
5+
import com.xpeho.spring_boot_java_random_user.data.sources.api.RemoteUserService;
56
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository;
67
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserSpecifications;
8+
import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers;
79
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
810
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
9-
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
11+
import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource;
12+
import com.xpeho.spring_boot_java_random_user.domain.services.UserService;
13+
import org.springframework.data.domain.Page;
14+
import org.springframework.data.domain.Pageable;
1015
import org.springframework.stereotype.Service;
1116

17+
import java.io.IOException;
1218
import java.util.List;
19+
import java.util.Map;
1320
import java.util.Optional;
21+
import java.util.function.Function;
22+
import java.util.stream.Collectors;
1423
import java.util.stream.StreamSupport;
1524

1625
@Service
17-
18-
public class UserServiceImpl implements LocalUserService {
26+
public class UserServiceImpl implements UserService {
1927
private final UserRepository userRepository;
2028
private final UserConverter userConverter;
29+
private final Map<UserSource, RemoteUserService> remoteUserServices;
2130

22-
public UserServiceImpl(UserRepository userRepository, UserConverter userConverter) {
31+
public UserServiceImpl(
32+
UserRepository userRepository,
33+
UserConverter userConverter,
34+
List<RemoteUserService> remoteUserServices
35+
) {
2336
this.userRepository = userRepository;
2437
this.userConverter = userConverter;
38+
this.remoteUserServices = remoteUserServices.stream()
39+
.collect(Collectors.toMap(RemoteUserService::getSource, Function.identity()));
2540
}
2641

2742
@Override
2843
public List<UserEntity> saveAll(List<UserEntity> users) {
29-
List<User> daoUsers = users.stream().map(userConverter::toDao).toList();
30-
Iterable<User> saved = userRepository.saveAll(daoUsers);
44+
List<UserDAO> userDAOs = users.stream().map(userConverter::toDao).toList();
45+
Iterable<UserDAO> saved = userRepository.saveAll(userDAOs);
3146
return StreamSupport.stream(saved.spliterator(), false)
3247
.map(userConverter::toDomain)
3348
.toList();
@@ -41,8 +56,8 @@ public Optional<UserEntity> getById(long id) {
4156

4257
@Override
4358
public UserEntity save(UserEntity user) {
44-
User savedUser = userRepository.save(userConverter.toDao(user));
45-
return userConverter.toDomain(savedUser);
59+
UserDAO savedUserDAO = userRepository.save(userConverter.toDao(user));
60+
return userConverter.toDomain(savedUserDAO);
4661
}
4762

4863
@Override
@@ -51,9 +66,19 @@ public void deleteById(long id) {
5166
}
5267

5368
@Override
54-
public List<UserEntity> filterUsers(UserFilter filter) {
55-
return userRepository.findAll(UserSpecifications.byFilter(filter)).stream()
56-
.map(userConverter::toDomain)
57-
.toList();
69+
public Page<UserEntity> filterUsers(UserFilter filter, Pageable pageable) {
70+
return userRepository.findAll(UserSpecifications.byFilter(filter), pageable)
71+
.map(userConverter::toDomain);
72+
}
73+
74+
@Override
75+
public PaginatedUsers fetchAndSaveUsers(int page, int size, UserSource source) throws IOException {
76+
RemoteUserService remoteService = remoteUserServices.get(source);
77+
if (remoteService == null) {
78+
throw new IllegalStateException("No remote service configured for source: " + source);
79+
}
80+
PaginatedUsers response = remoteService.fetchUsers(page, size);
81+
saveAll(response.data());
82+
return response;
5883
}
5984
}

src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RemoteUserService.java renamed to src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RemoteUserService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.xpeho.spring_boot_java_random_user.domain.services;
1+
package com.xpeho.spring_boot_java_random_user.data.sources.api;
22

33
import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers;
44
import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.xpeho.spring_boot_java_random_user.data.sources.api;
2+
3+
import com.xpeho.spring_boot_java_random_user.data.sources.api.dummy.DummyUserApi;
4+
import com.xpeho.spring_boot_java_random_user.data.sources.api.randomuser.RandomUserApi;
5+
import jakarta.annotation.PreDestroy;
6+
import okhttp3.OkHttpClient;
7+
import org.springframework.beans.factory.annotation.Qualifier;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.core.env.Environment;
11+
import retrofit2.Retrofit;
12+
import retrofit2.converter.gson.GsonConverterFactory;
13+
14+
@Configuration
15+
public class UserApiConfig {
16+
17+
private OkHttpClient okHttpClient;
18+
19+
/**
20+
* Single shared OkHttpClient intentionally reused by both Retrofit instances (dummyUserRetrofit and randomUserRetrofit).
21+
* Sharing a single client allows both APIs to benefit from a common connection pool, thread pool,
22+
* and keep-alive settings, reducing resource consumption.
23+
* If the two APIs ever require distinct timeouts or interceptors, separate clients should be created.
24+
* The {@link jakarta.annotation.PreDestroy} hook ensures the client is shut down cleanly on application stop.
25+
*/
26+
@Bean
27+
public OkHttpClient okHttpClient() {
28+
okHttpClient = new OkHttpClient.Builder().build();
29+
return okHttpClient;
30+
}
31+
32+
@Bean(name = "dummyUserRetrofit")
33+
public Retrofit dummyUserRetrofit(OkHttpClient okHttpClient, Environment env) {
34+
return new Retrofit.Builder()
35+
.baseUrl(env.getRequiredProperty("dummy.api.base-url"))
36+
.client(okHttpClient)
37+
.addConverterFactory(GsonConverterFactory.create())
38+
.build();
39+
}
40+
41+
@Bean
42+
public DummyUserApi dummyUserApi(@Qualifier("dummyUserRetrofit") Retrofit dummyUserRetrofit) {
43+
return dummyUserRetrofit.create(DummyUserApi.class);
44+
}
45+
46+
@Bean(name = "randomUserRetrofit")
47+
public Retrofit randomUserRetrofit(OkHttpClient okHttpClient, Environment env) {
48+
return new Retrofit.Builder()
49+
.baseUrl(env.getRequiredProperty("randomuser.api.base-url"))
50+
.client(okHttpClient)
51+
.addConverterFactory(GsonConverterFactory.create())
52+
.build();
53+
}
54+
55+
@Bean
56+
public RandomUserApi randomUserApi(@Qualifier("randomUserRetrofit") Retrofit randomUserRetrofit) {
57+
return randomUserRetrofit.create(RandomUserApi.class);
58+
}
59+
60+
@PreDestroy
61+
public void destroy() {
62+
if (okHttpClient != null) {
63+
okHttpClient.dispatcher().executorService().shutdown();
64+
okHttpClient.connectionPool().evictAll();
65+
}
66+
}
67+
}

src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers;
77
import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource;
88
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
9-
import com.xpeho.spring_boot_java_random_user.domain.services.RemoteUserService;
9+
import com.xpeho.spring_boot_java_random_user.data.sources.api.RemoteUserService;
1010
import org.springframework.stereotype.Service;
1111
import retrofit2.Response;
1212

0 commit comments

Comments
 (0)