• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Decoupling Business Logic from Database Access with Dependency Inversion

Lomanu4 Оффлайн

Lomanu4

Команда форума
Администратор
Регистрация
1 Мар 2015
Сообщения
1,481
Баллы
155
The Problem with Direct Database Coupling


In many applications, business logic directly depends on database access, creating tight coupling:


// Traditional approach - Business logic depends on database
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Direct dependency on database

public void upgradeUserSubscription(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));

if (user.canUpgrade()) {
user.setSubscriptionLevel(SubscriptionLevel.PREMIUM);
userRepository.save(user);
}
}
}

This approach has several problems:

  • Business logic is tightly coupled to database implementation
  • Difficult to test without a database
  • Changes to persistence layer affect business logic
  • Hard to switch to different storage solutions
The Solution: Dependency Inversion


Let's apply the Dependency Inversion Principle using ports and adapters (hexagonal architecture):

  1. First, define the business domain model:

public class UserDomain {
private final Long id;
private final String email;
private SubscriptionLevel subscriptionLevel;

// Constructor and methods...

public boolean canUpgrade() {
return subscriptionLevel == SubscriptionLevel.BASIC;
}

public void upgradeToPremium() {
if (!canUpgrade()) {
throw new BusinessException("User cannot be upgraded");
}
this.subscriptionLevel = SubscriptionLevel.PREMIUM;
}
}
  1. Create a port (interface) that business logic will use:

public interface UserPort {
UserDomain findUser(Long userId);
void saveUser(UserDomain user);
}
  1. Implement business logic that depends on the port:

@Service
public class UserService {
private final UserPort userPort;

public UserService(UserPort userPort) {
this.userPort = userPort;
}

public void upgradeUserSubscription(Long userId) {
UserDomain user = userPort.findUser(userId);
user.upgradeToPremium();
userPort.saveUser(user);
}
}
  1. Create a database adapter that implements the port:

@Component
public class UserDatabaseAdapter implements UserPort {
private final UserRepository userRepository;
private final UserMapper userMapper;

public UserDatabaseAdapter(UserRepository userRepository, UserMapper userMapper) {
this.userRepository = userRepository;
this.userMapper = userMapper;
}

@Override
public UserDomain findUser(Long userId) {
User userEntity = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
return userMapper.toDomain(userEntity);
}

@Override
public void saveUser(UserDomain userDomain) {
User userEntity = userMapper.toEntity(userDomain);
userRepository.save(userEntity);
}
}
  1. Create a mapper to convert between domain and entity:

@Component
public class UserMapper {
public UserDomain toDomain(User entity) {
return new UserDomain(
entity.getId(),
entity.getEmail(),
entity.getSubscriptionLevel()
);
}

public User toEntity(UserDomain domain) {
User entity = new User();
entity.setId(domain.getId());
entity.setEmail(domain.getEmail());
entity.setSubscriptionLevel(domain.getSubscriptionLevel());
return entity;
}
}
  1. Define the JPA entity:

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String email;

@Enumerated(EnumType.STRING)
private SubscriptionLevel subscriptionLevel;

// Getters and setters
}
Benefits of This Approach


  1. Pure Business Logic:
    • Business rules are isolated in the domain model
    • No dependencies on infrastructure concerns
    • Easy to test without mocking database calls

  2. Flexibility:
    • Easy to swap database implementations
    • Can add caching or event publishing without touching business logic
    • Can implement different adapters for different storage solutions

  3. Testing:

public class UserServiceTest {
private UserService userService;
private UserPort userPort;

@BeforeEach
void setUp() {
userPort = new InMemoryUserPort(); // Test implementation
userService = new UserService(userPort);
}

@Test
void shouldUpgradeUserSubscription() {
// Given
UserDomain user = new UserDomain(1L, "test@test.com", SubscriptionLevel.BASIC);
((InMemoryUserPort) userPort).addUser(user);

// When
userService.upgradeUserSubscription(1L);

// Then
UserDomain updatedUser = userPort.findUser(1L);
assertEquals(SubscriptionLevel.PREMIUM, updatedUser.getSubscriptionLevel());
}
}
  1. Alternative Implementations:

@Component
@Profile("cache")
public class CachedUserAdapter implements UserPort {
private final UserPort databaseAdapter;
private final Cache cache;

@Override
public UserDomain findUser(Long userId) {
return cache.get(userId, () -> databaseAdapter.findUser(userId));
}

@Override
public void saveUser(UserDomain user) {
databaseAdapter.saveUser(user);
cache.put(user.getId(), user);
}
}
Configuration


@Configuration
public class UserConfig {
@Bean
public UserPort userPort(UserRepository repository, UserMapper mapper) {
return new UserDatabaseAdapter(repository, mapper);
}

@Bean
public UserService userService(UserPort userPort) {
return new UserService(userPort);
}
}
To conclude


By applying dependency inversion:

  • Business logic becomes pure and focused
  • Testing becomes easier
  • The system becomes more flexible
  • Different storage implementations can be swapped easily
  • The code is more maintainable and follows SOLID principles

Remember:

  • Domain models should be persistence-ignorant
  • Business rules should be in the domain model
  • Adapters handle infrastructure concerns
  • Use interfaces (ports) to define boundaries
  • Keep the domain model focused on business behavior

This architecture makes your code more resilient to change and easier to maintain over time.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу