DEV Community

Cover image for Connascense
Bruno Macedo
Bruno Macedo

Posted on Edited on

Connascense

Reading Fundamentals of Software Architecture I notice interesting topic about Connascense, nowadays we use some patterns to distribute the code or the system to be a microsservice, offering scalability and governance, but in some situations generates Coupling, this can be understood by 9 different types of connascence, it follows a degree.

"Two components are connascent if a change in one would require the other to be modified in order to
maintain the overall correctness of the system."

Classification of Connascense

The idea is not to eliminate the Connascense from your code, but keep him lowest possible from the highest ones.

Connascence of Name (CoN)
Many places dependent on the same function/method name generate the CoN


public class ElectronicWaiter {

    public void sendOrderToKitchen(Order order) {
        ...
    }
}

public class OrderService {

    private final ElectronicWaiter electronicWaiter;

    public void sendToKitchen(Order order) {
        electronicWaiter.sendOrderToKitchen(order);
    }
}

Enter fullscreen mode Exit fullscreen mode

CoN is generated when the sendOrderToKitchen is being called from another function, if its name changes, will break the function.

You can solve this using a interface, it uses the interface instead of class.

public interface Waiter {

    void sendOrderToKitchen(Order order);
}

public class ElectronicWaiter implements Waiter {

    @Override
    public void sendOrderToKitchen(Order order) {
        ...
    }
}

public class OrderService {

    private final Waiter waiter;

    public OrderService(Waiter waiter) {
        this.waiter = waiter;
    }

    public void sendToKitchen(Order order) {
        waiter.sendOrderToKitchen(order);
    }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Type (CoT)
Two or more components agree on the type of an entity

public class PaymentService {
    public Receipt processPayment(Payment payment) { ... }
}

public class OrderService {
    public void checkout(Payment payment) {
        Receipt receipt = paymentService.processPayment(payment);
    }
}
Enter fullscreen mode Exit fullscreen mode

Both services need to return Payment Model and the paramether type is Payment, if you need to change any of the types it will break the functions communications. To solve you can decrease to CoN or use interfaces, specific DTO per layer, Primitive Obsession.

public record PaymentRequest(String orderId, BigDecimal amount, String paymentMethod);

public record PaymentReceipt(String receiptId, BigDecimal amount, LocalDateTime processedAt);

public interface PaymentService {
    PaymentReceipt processPayment(PaymentRequest request);
}

public class PaymentServiceImpl implements PaymentService {

    @Override
    public PaymentReceipt processPayment(PaymentRequest request) {
        ...
    }
}

public class OrderService {

    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void checkout(PaymentRequest paymentRequest) {
        PaymentReceipt receipt = paymentService.processPayment(paymentRequest);
        ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Meaning (CoM)
Two or more components need to agree on the meaning of values, this happens more on magic strings/numbers and implicit conventions.

public class RoomService {
    public int checkRoomAccess(Long roomId, Long userId) {
        return 1;
    }
}

public class BookingController {

    public void createBooking(Long roomId, Long userId) {
        int access = roomService.checkRoomAccess(roomId, userId);

        if (access == 1) {
            confirmBooking();
        } else if (access == 2) {
            throw new AccessDeniedException("User not have permission");
        } else if (access == 3) {
            throw new RoomLockedException("Room blocked");
      }
}
Enter fullscreen mode Exit fullscreen mode

See the numbers working as a controllers, if something change the numbers it will break the entire logic for Booking. Solve this using a Enum class

public enum RoomAccessResult {
    ALLOWED,
    DENIED,
    ROOM_LOCKED
}
Enter fullscreen mode Exit fullscreen mode
public class RoomService {
    public int checkRoomAccess(Long roomId, Long userId) {
        return RoomAccessResult.ALLOWED;
    }
}

public class BookingController {

    public void createBooking(Long roomId, Long userId) {
        int access = roomService.checkRoomAccess(roomId, userId);

        if (access == RoomAccessResult.ALLOWED) {
            confirmBooking();
        } else if (access == RoomAccessResult.DENIED) {
            throw new AccessDeniedException("User not have permission");
        } else if (access == RoomAccessResult.ROOM_LOCKED) {
            throw new RoomLockedException("Room blocked");
      }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Algorithm (CoA)

Multiple components must agree on a specific algorithm, it is common to be found on systems that consumes from various teams or code that is more distributed.

public class ShippingCalculator {

    public double calculateFreight(double distanceInKm, double weightInKg) {
        double basePrice = distanceInKm * 1.8;
        double weightFactor = weightInKg * 0.95;
        double total = basePrice + weightFactor;

        if (distanceInKm > 100) {
            total *= 0.93;
        }
        if (weightInKg > 500) {
            total += 120;
        }
        return Math.round(total * 100.0) / 100.0;
    }
}


public class OrderTotalService {

    public double calculateTotalWithShipping(Order order) {
        double freight = shippingCalculator.calculateFreight(
            order.getDistanceInKm(), 
            order.getTotalWeight()
        );

        if (order.getDistanceInKm() > 100 && order.getTotalWeight() < 10) {
            freight = 0.0;
        }

        return order.getSubtotal() + freight;
    }
}
Enter fullscreen mode Exit fullscreen mode

Both algorithms need to match the same calculation around the shipping value. If a detail within one of these functions changes, it will be necessary to change both.
Can solve this problem extracting to a dedicated class.

public class FreightPolicy {
    public double calculate(double distanceInKm, double weightInKg) { ... }
    public boolean isFreeShipping(double distanceInKm, double weightInKg) { ... }
}
Enter fullscreen mode Exit fullscreen mode

Or use a Strategy Pattern from Design Patterns https://refactoring.guru/pt-br/design-patterns/strategy

Connascence of Position (CoP)
Multiples components need to match to the position of elements or values, that is, a code will not work if the order is changed to work as expected.

new Person("Bruno", "bruno@email.com", 22);

new Person("bruno@email.com", 22, "Bruno"); 
Enter fullscreen mode Exit fullscreen mode

The best approach to solve this is using a Builder pattern, it depends on which class CoP was generated, but at this example of Users it is totally acceptable.


public class Person {
    private final String name;
    private final String email;
    private final int age;

    private Person(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
    }

    public static class Builder {
        private String name;
        private String email;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }

    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
}

Enter fullscreen mode Exit fullscreen mode
Person person = new Person.Builder()
            .name("Bruno")
            .email("bruno@email.com")
            .age(22)
            .build();
Enter fullscreen mode Exit fullscreen mode

Connascence of Execution (CoEx)
To avoid confusion with CoP, note that CoEx is similar but focuses on the order of execution. The system only works correctly if certain pieces of code are executed in a specific sequence.

public class OrderProcessor {

    public void processOrder(Order order) {
        validateOrder(order);
        reserveInventory(order);
        chargePayment(order);
        sendConfirmation(order);
    }

    private void validateOrder(Order order) { ... }
    private void reserveInventory(Order order) { ... }
    private void chargePayment(Order order) { ... }
    private void sendConfirmation(Order order) { ... }
}
Enter fullscreen mode Exit fullscreen mode

Solution for this is create a orchestrator class.

public class OrderProcessingWorkflow {

    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final NotificationService notificationService;

    public OrderProcessingWorkflow(InventoryService inventoryService,
                                   PaymentService paymentService,
                                   NotificationService notificationService) {
        this.inventoryService = inventoryService;
        this.paymentService = paymentService;
        this.notificationService = notificationService;
    }

    public OrderResult execute(Order order) {
        validate(order);

        InventoryReservation reservation = inventoryService.reserve(order);

        PaymentReceipt receipt = paymentService.charge(order, reservation);

        notificationService.sendConfirmation(order, receipt);

        return new OrderResult("SUCCESS", receipt.getId());
    }

    private void validate(Order order) {
        if (!order.isValid()) {
            throw new InvalidOrderException("Order is invalid");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Timing (CoTi)
When a system pattern depends on the execution moment, involves a range of time between actions, if one of them is not executed, it can result in actions being lost within the system. This happens when we have a multiple process, threads that modify the same state without proper synchronization.

@Service
public class InventoryService {

    public boolean reserveProduct(Long productId, int quantity) {
        Product product = productRepository.findById(productId).orElseThrow();

        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
            return true;
        }
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Can be resolved by restructuring method to be a transaction.

@Service
public class InventoryService {

    @Transactional
    public boolean reserveProduct(Long productId, int quantity) {
        Product product = productRepository.findByIdWithLock(productId)
                .orElseThrow();

        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
            return true;
        }
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Values (CoV)
Occurs when multiple components must agree on the same main value. If this value changes in one place, other locations can be affected due to the utilization. Can be quite dangerous because duplicated magic values are easy to forget to update and cause problems.

public class OrderService {
    public void processOrder(Order order) {
        if (order.getStatus() == 2) {           // 2 = Status Approved
            chargeCustomer(order);
        }
    }
}

public class NotificationService {
    public void sendNotification(Order order) {
        if (order.getStatus() == 2) {           // 2 = Status Approved
            sendEmail(order);
        }
    }
}

public class ReportService {
    public boolean isApproved(Order order) {
        return order.getStatus() == 2;          // 2 = Status Approved
    }
}
Enter fullscreen mode Exit fullscreen mode

A good solution for this is a centralized Enum that handle the status values and all components point to this class.

public enum OrderStatus {
    PENDING(1),
    APPROVED(2),
    REJECTED(3),
    CANCELLED(4),
    DELIVERED(5);

    private final int code;

    OrderStatus(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

public class OrderService {
    public void processOrder(Order order) {
        if (order.getStatus() == OrderStatus.APPROVED) {
            chargeCustomer(order);
        }
    }
}

public class NotificationService {
    public void sendNotification(Order order) {
        if (order.getStatus() == OrderStatus.APPROVED) {
            sendEmail(order);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Connascence of Identity (CoI)
Occurs when multiple components refer to the exact same instance of an object.
For example, when an object is created in one place (such as a controller or factory) and then the same instance is passed through different services, validators, processors, utilities, and so on, until it reaches the database or is used in an external request.
This approach exposes the system to several risks: it becomes difficult to track which component made which change, and any method can modify the object without the knowledge of other components, often leading to unexpected side effects.

@RestController
public class OrderController {

    @PostMapping("/orders")
    public ResponseEntity<?> createOrder(@RequestBody OrderRequest request) {
        Order order = new Order();
        order.setCustomerId(request.getCustomerId());

        orderService.enrichOrder(order);
        validationService.validate(order);
        discountService.applyDiscounts(order);
        orderService.save(order);

        return ok();
    }
}
Enter fullscreen mode Exit fullscreen mode

This problem can be solved by adopting an immutability-based flow:

Order order = orderFactory.createFrom(request);

order = orderService.enrichOrder(order);
order = discountService.applyDiscounts(order);
order = taxService.applyTaxes(order);

orderRepository.save(order);
Enter fullscreen mode Exit fullscreen mode

Instead of modifying the same instance throughout the entire process, each method receives an object, makes its changes, and returns a new (or updated) instance.

public Order enrichOrder(Order order) {
    Order enriched = order.copy();
    enriched.setSomeField(...);
    enriched.setAnotherField(...);
    return enriched;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)