Leaders Logo

Connascence in .NET: How to Reduce Coupling Without Losing Delivery Speed

Introduction

Excessive coupling remains one of the main causes of fragility in enterprise systems. In modern platforms like .NET, the challenge is not only to build quickly, but to allow for safe evolution over time. The concept of Connascence offers a technical vocabulary to diagnose dependencies and guide refactoring with architectural criteria (PAGE-JONES, 1992).

In this article, we explore how to apply Connascence in APIs, services, and event-driven flows in .NET 10, showing practices for shifting coupling from distributed areas to more local and controllable points (EVANS, 2003).

Connascence Fundamentals

Definition and motivation

Connascence describes the degree of shared knowledge required between parts of a system for it to function correctly. The greater the shared knowledge and the further apart it is in the system, the higher the risk of regression in future changes (PAGE-JONES, 1992).

Main types of Connascence

In everyday work with .NET, the most common types appear as:

  • Connascence of Name (CoN): dependency on exact names (methods, properties, JSON fields).
  • Connascence of Type (CoT): dependency on specific types and formats.
  • Connascence of Meaning (CoM): dependency on implicit conventions, without explicit documentation.
  • Connascence of Execution/Timing (CoE/CoTg): dependency on execution order and timing.
  • Connascence of Algorithm (CoA): multiple points replicating the same algorithm.
  • Connascence of Position (CoP): dependency on the position of arguments or fields.

The goal is not to eliminate all connascence, but to reduce its most harmful forms and, when unavoidable, keep them in local and highly testable scopes (FOWLER, 2002).

Connascence in .NET APIs

Example of coupling by position and meaning

A recurring antipattern is to expose APIs with positional parameters without explicit semantics:

// Antipattern: high risk of CoP and CoM
public sealed class PricingService
{
    public decimal Calculate(decimal value, decimal discount, decimal tax)
        => (value - discount) * (1 + tax);
}

// Fragile call: the wrong order compiles and fails only at runtime/logic
var total = pricing.Calculate(100m, 0.2m, 10m);

Refactoring to an explicit contract reduces ambiguities and makes evolution easier:

public sealed record class PricingInput
{
    public required decimal GrossAmount { get; init; }
    public required decimal DiscountAmount { get; init; }
    public required decimal TaxRate { get; init; }
}

public sealed class PricingService
{
    public decimal Calculate(PricingInput input)
        => (input.GrossAmount - input.DiscountAmount) * (1 + input.TaxRate);
}

var total = pricing.Calculate(new PricingInput
{
    GrossAmount = 100m,
    DiscountAmount = 10m,
    TaxRate = 0.2m
});

Versioning and name connascence

Changing field names in public contracts creates distributed CoN between providers and consumers. In .NET 10, the safest path remains additive evolution with versioned DTOs by namespace and explicit serialization, maintaining backward compatibility (NEWMAN, 2021).

namespace Contracts.V1;

public sealed record class OrderDto
{
    public required Guid Id { get; init; }
    public required decimal TotalAmount { get; init; }
}

namespace Contracts.V2;

public sealed record class OrderDto
{
    public required Guid Id { get; init; }
    public required decimal TotalAmount { get; init; }
    public string? Currency { get; init; }
}

Connascence in Distributed Architectures

Events and temporal connascence

In messaging, assuming a fixed order of events between different services is a dangerous form of CoE/CoTg. Instead, prefer self-describing messages, idempotency, and state-oriented processing rather than strict sequencing (VERNON, 2013).

public sealed record class PaymentReceivedV1
{
    public required Guid OrderId { get; init; }
    public required decimal Amount { get; init; }
    public required DateTimeOffset OccurredAt { get; init; }
    public required string EventId { get; init; }
}

public sealed class PaymentProjection
{
    private readonly HashSet<string> _processed = [];

    public bool TryApply(PaymentReceivedV1 evt)
    {
        if (!_processed.Add(evt.EventId))
        {
            return false; // Idempotency avoids dependency on timing and repetition
        }

        // Updates state deterministically
        return true;
    }
}

Canonical contracts as coupling reducers

When several systems exchange data with similar semantics, a canonical model reduces CoM and CoN because it makes ubiquitous language and transformation rules explicit at well-known points. This arrangement usually works better when combined with dedicated mappers and automated contract tests (EVANS, 2003).

Practical Strategies for .NET Teams

1. Measure connascence in technical reviews

During code review, ask: does this change add dependency on name, order, type, or timing between distant modules? If so, reconsider the design before scaling to production.

2. Localize strong connascence within the same module

Strong connascence is less harmful when confined to the same component, as coordinated changes occur within the same deployment cycle. This principle aligns with the recommendation to increase cohesion and reduce cross-cutting coupling (MARTIN, 2017).

3. Automate contract verification

In .NET pipelines, serialization and version compatibility tests prevent silent regressions:

public sealed class ContractCompatibilityTests
{
    [Fact]
    public void V2_Should_Deserialize_V1_Payload()
    {
        var v1Json = """
        {
          "Id": "7a6a16de-31c0-4770-9413-6f6cefcf7a3a",
          "TotalAmount": 125.50
        }
        """;

        var dto = JsonSerializer.Deserialize<Contracts.V2.OrderDto>(v1Json);

        Assert.NotNull(dto);
        Assert.Equal(125.50m, dto!.TotalAmount);
        Assert.Null(dto.Currency);
    }
}

4. Reduce algorithmic connascence with unique services

When the same rule appears in multiple places, centralize it in a single domain service to avoid distributed CoA and semantic divergence between teams.

Conclusion

Connascence offers a pragmatic lens for evolving .NET systems with less regression and more predictability. By identifying dependencies by name, type, meaning, timing, and algorithm, technical teams make design decisions based on real maintenance risk, not just stylistic preference.

In corporate scenarios, combining canonical contracts, additive versioning, idempotency, and compatibility testing transforms coupling from a hidden problem into a manageable asset. The result is a more resilient architecture, capable of growing without sacrificing team autonomy or the stability of existing integrations.

References

  • PAGE-JONES, Meilir. The practical guide to structured systems design. 2nd ed. Englewood Cliffs: Yourdon Press, 1992. reference.Description
  • EVANS, Eric. Domain-driven design: tackling complexity in the heart of software. Boston: Addison-Wesley, 2003. reference.Description
  • FOWLER, Martin. Patterns of enterprise application architecture. Boston: Addison-Wesley, 2002. reference.Description
  • NEWMAN, Sam. Building microservices: designing fine-grained systems. 2nd ed. Sebastopol: O'Reilly Media, 2021. reference.Description
  • VERNON, Vaughn. Implementing domain-driven design. Boston: Addison-Wesley, 2013. reference.Description
  • MARTIN, Robert C. Clean Architecture: A Craftsman's Guide to Software Structure and Design. Boston: Pearson, 2017. reference.Description
About the author