Content Developer II at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
129942 stories
·
29 followers

You’re not as loosely coupled as you think!

1 Share

When you hear tight coupling or being loosely coupled, what does that even mean? There are many aspects to coupling that you need to think about. Temporal, Data Schema, Location, Technology. Removing one aspect usually involves having to deal with a whole new set of problems.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Technology Coupling

When it comes to building better software systems, managing coupling is at the forefront. Many developers often think about coupling primarily in terms of dependencies; for instance, how system A depends on system B, and so on. However, the reality is that coupling involves multiple dimensions that go beyond simple dependency chains. In this post, we will explore the various aspects of coupling, especially in the context of microservices, and discuss how to navigate the complexities that arise.

Let’s start with a typical illustration of coupling in microservices. Imagine a service acting as a client, making HTTP calls to another service that exposes an API.

At first glance, it may seem like this setup inherently reduces coupling. While it does help in certain ways, we must dive deeper into what coupling truly means. Gregor Hohpe’s definition of coupling describes it as the “independent variability of connected systems.” In simpler terms, if a change in system A impacts system B, then we have a coupling issue.

Introducing an HTTP call between different services can eliminate some technology coupling, especially if one service is built on .NET and another on the JVM. The network call allows for varying technology stacks to interact, which is a form of decoupling. However, it doesn’t eliminate all forms of coupling.

Temporal

For instance, consider the temporal aspect of coupling.

When discussing message queues and event-driven architectures, where the availability of services does not need to align perfectly.

While service A may place a message on a queue for service B to process later, the dependency still exists; service A must know that service B will process the message eventually.

Many developers tout the publish-subscribe model as an example of loose coupling, where producers and consumers operate independently. This redecues coupling from the publisher because it does not know or care about who the consumers are of the events it publishes.

However, this assertion is somewhat misleading. If the producer stops publishing an event that a consumer depends on, the latter will break. Similarly, if the data structure or format of the message changes, service B must adapt accordingly. This dependency indicates that coupling is still present, albeit in a different form.

Location

We also need to consider location coupling. When making HTTP requests, a service must know the URI, hostname, or IP address of the other service. This knowledge creates a coupling that can become problematic if the service’s location changes.

This is also applicable with any other form of communication such as queues or topics. Publishers and consumers must be aware of the location and how to connect othe message broker, queue and/or topics.

One way to mitigate this is through the use of service registries, which can help abstract away some of the location dependencies.

Data Structure & Format

Data structure coupling is another key aspect. If you’re using an HTTP API or messaging you’re expecting a particular structure/schema to consumer it. Similar to database schemas, you have ot manage how you want to change them in a backward compatible way. Adding new properties can be fine however renaming, removing or changing the structure can cause clients to fail.

If a service expects a specific format, such as JSON, any changes to that format can lead to coupling issues. Content negotiation can help alleviate this problem, but it still requires the client to understand the expected structure of the data.

Tight & Loose

Now, let’s consider a practical example involving Firebase Cloud Messaging APIs. During a migration from legacy to newer versions, I discovered that the way authentication was handled had changed significantly. This shift created technology coupling with a new .NET package that I had to use. By opting to use HTTP client directly instead, I faced multiple layers of coupling: changes in the data structure, the URI where requests were sent, and the overall response format.

Understanding the various aspects of coupling—temporal, location, technology, and data—is essential for software developers. Each aspect presents its own challenges and trade-offs, and simply removing one type of coupling often introduces new problems. For instance, while moving to an event-driven architecture might help with temporal coupling, it can create new dependencies that need to be managed.

As you navigate the landscape of software architecture, remember that coupling is a multifaceted issue. It’s not enough to focus on just one aspect; you must consider how changes in one area can ripple through the entire system. By taking a holistic view of coupling, you can make informed decisions that lead to more resilient and maintainable systems.

Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

The post You’re not as loosely coupled as you think! appeared first on CodeOpinion.

Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete

Preparing Documents for E-Signing for Multiple Signers in .NET C#

1 Share

Learn how to prepare documents for e-signing by multiple signers in .NET C#. This article shows how to create signature fields and how to assign them to signers.

Read more



Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete

Daily Reading List – November 13, 2024 (#440)

1 Share

Another day of interesting content. There’s a fair bit of AI, but plenty of other topics as well.

[blog] Our Machine Learning Crash Course goes in depth on generative AI. This is a remarkable free resource. It’s a self-study course taken by millions, and now refreshed with the latest AI advances. I might take it myself!

[blog] 65,000 nodes and counting: Google Kubernetes Engine is ready for trillion-parameter AI models. Wow. Just wow. This record-breaking cluster size is only possible because of the uniquely awesome platform foundation in Google Cloud.

[blog] Introducing enhanced Dataproc Serverless runtimes for a streamlined ML development experience. We’ve added a fresh set of libraries to our serverless Spark service so that there’s less for you to do.

[blog] Deployment-Driven Development. Tyler says that we should change our thinking. Instead of treating deployments as a destination, it should be prioritized from the start.

[article] Employee AI adoption cools globally. Temporarily. This survey from Slack shows that folks don’t want to be perceived by management as lazy for using AI. We can fix that with better education of the management ranks, and better sponsorship of AI initiatives from exec teams.

[blog] Computer Use with Anthropic’s Newest Model on Vertex AI. I’m familiar with this cool Anthropic feature, but Nikita helps us really see how to use it.

[blog] Virtual Personas for Language Models via an Anthology of Backstories. The AI research team at Berkeley proposes an interesting idea by giving LLMs a backstory.

[blog] Why Developers Are Unresponsive to Traditional Marketing. Adam gives some candid, necessary truth to those who are trying to coax developers into their universe.

[article] Memory in Agent Systems. This is a good topic to learn about. With all this talk about agents, how do we think about durable storage of “memory”?

[article] Google DeepMind open-sources AlphaFold 3, ushering in a new era for drug discovery and molecular biology. A lot of our work is for commercial gain, and I make no apologies for it. But I love how much we do that’s just about discovery and advancing ideas.

Want to get this update sent to you every day? Subscribe to my RSS feed or subscribe via email below:



Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete

Introducing the .NET Aspire Community Toolkit

1 Share

We’re excited to announce the official release of the .NET Aspire Community Toolkit which you can get on NuGet and supports .NET Aspire 9! The Community Toolkit contains a collection of integrations and extensions that help you build with .NET Aspire.

🤔 What is the .NET Aspire Community Toolkit?

.NET Aspire has grown massively since it was first released, tackling more and more scenarios, which in turn brings the need for more integrations to support these kinds of applications. While many of these integrations are built by the .NET Aspire team, they can’t cover everything. That’s where the .NET Aspire Community Toolkit comes in.

The team behind the Community Toolkit wanted to create a place where third parties could contribute their own integrations and extensions to the .NET Aspire ecosystem. This way, the community can help shape the future of .NET Aspire and make it even more powerful, freeing the focus of the .NET Aspire team to work on the core framework. We’ve put a lot of effort into making the Community Toolkit easy to use and contribute to, and easy to maintain through examples, documentation, testing, and more.

🚀 What’s in the .NET Aspire Community Toolkit?

Today, the Community Toolkit contains nearly a dozen integrations across both hosting and client, with more being added all the time. Here are some of the highlights:

You can find all the integrations available in the Community Toolkit on the official documentation, or by checking out the GitHub repository.

🎉 Get started with the .NET Aspire Community Toolkit

To get started with the .NET Aspire Community Toolkit, you can install it from NuGet via the Visual Studio tooling, VS Code tooling or the .NET CLI. Let’s take the the Ollama hosting integration as an example. Navigate to your app host project and install the package:

Or via the command line:

dotnet add package CommunityToolkit.Aspire.Hosting.Ollama

Then, you can configure the Ollama hosting integration in your Program.cs file:

var ollama =
        builder.AddOllama("ollama")
               .WithDataVolume()
               .WithOpenWebUI();

var llama = ollama.AddModel("llama3.2");

builder.AddProject<Projects.MyApi>("api")
       .WithReference(llama);

Now you can provide the ollama resource to any of the other services in the app host and consume it with a client library, like OllamaSharp (which we have an integration for too!).

🤝 Contribute to the .NET Aspire Community Toolkit

The .NET Aspire Community Toolkit couldn’t exist without the community. Big thanks to @justinyoo, @tommasodotNET, @FBoucher, @Alirexaa and @Mrxx99 for their contributions to the project so far!

But of course, we welcome contributions from the community. If you have an integration or extension that you think would be useful to others, we’d love to see it! You can find out more about contributing on the GitHub repository.

So come and join us on GitHub, give us a star ⭐, and let’s create some awesome integrations together!

The post Introducing the .NET Aspire Community Toolkit appeared first on .NET Blog.

Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete

The puzzle of trying to put an object into a std::optional

1 Share

The C++ standard library template type std::optional<T> has one of two states. It could be empty (not contain anything), or it could contain a T.

Suppose you start with an empty std::optional<T>. How do you put a T into it?

One of my colleagues tried to do it in what seemed to be the most natural way: Use the assignment operator.

struct Doodad
{
    Doodad();
    ~Doodad();
    std::unique_ptr<DoodadStuff> m_stuff;
};

struct Widget
{
    std::optional<Doodad> m_doodad;

    Widget()
    {
        if (doodads_enabled()) {
            // I guess we need a Doodad too.
            Doodad d;
            m_doodad = d;
        }
    }
};

Unfortunately, the assignment failed to compile:

Widget.cpp: error C2679: binary '=': no operator found which takes a right-hand operand of type 'Doodad' (or there is no acceptable conversion)

I asked for the rest of the error message, because the details will explain what the compiler tried to do (and why it couldn’t). It’s long, but we’ll walk through it.

    optional(617,1):
    could be 'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<Doodad> &)'
        Widget.cpp(100,9):
        'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<Doodad> &)': cannot convert argument 2 from 'Doodad' to 'const std::optional<Doodad> &'
            Widget.cpp(100,27):
            Reason: cannot convert from 'Doodad' to 'const std::optional<Doodad>'
            Widget.cpp(100,27):
            No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    optional(283,28):
    or       'std::optional<Doodad> &std::optional<Doodad>::operator =(std::nullopt_t) noexcept'
        Widget.cpp(100,9):
        'std::optional<Doodad> &std::optional<Doodad>::operator =(std::nullopt_t) noexcept': cannot convert argument 2 from 'Doodad' to 'std::nullopt_t'
            Widget.cpp(100,27):
            No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    optional(321,28):
    or       'std::optional<Doodad> &std::optional<Doodad>::operator =(std::optional<_Ty2> &&) noexcept(<expr>)'
        Widget.cpp(100,9):
        'std::optional<Doodad> &std::optional<Doodad>::operator =(std::optional<_Ty2> &&) noexcept(<expr>)': could not deduce template argument for 'std::optional<_Ty2> &&' from 'Doodad'
    optional(307,28):
    or       'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<_Ty2> &) noexcept(<expr>)'
        Widget.cpp(100,9):
        'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<_Ty2> &) noexcept(<expr>)': could not deduce template argument for 'const std::optional<_Ty2> &' from 'Doodad'
    optional(292,28):
    or       'std::optional<Doodad> &std::optional<Doodad>::operator =(_Ty2 &&) noexcept(<expr>)'
        Widget.cpp(100,9):
        'std::optional<Doodad> &std::optional<Doodad>::operator =(_Ty2 &&) noexcept(<expr>)': could not deduce template argument for '__formal'
            optional(288,33):
            'std::enable_if_t<false,int>' : Failed to specialize alias template
    Widget.cpp(100,9):
    while trying to match the argument list '(std::optional<Doodad>, Doodad)'

The compiler is showing its work. It’s showing you all the possible overloaded assignment operators and explained why each one failed. The way to understand what went wrong is to look for the overload you intended to use and see why the compiler rejected it. Let’s take them one at a time.

could be 'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<Doodad> &)'
    cannot convert argument 2 from 'Doodad' to 'const std::optional<Doodad> &'
    Reason: cannot convert from 'Doodad' to 'const std::optional<Doodad>'
    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

The first assignment operator available is the one where you assign a std::optional<Doodad> to another std::optional<Doodad>. This one failed because you passed a Doodad, not a std::optional<Doodad>, and there was no eligible conversion.

Okay, what’s next?

or 'std::optional<Doodad> &std::optional<Doodad>::operator =(std::nullopt_t) noexcept'
    'std::optional<Doodad> &std::optional<Doodad>::operator =(std::nullopt_t) noexcept': cannot convert argument 2 from 'Doodad' to 'std::nullopt_t'
    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

This is the emptying assignment, where you can assign a std::nullopt to the optional to return it to the empty state. This is not what we wanted either, so we’re not surprised that it failed.

Onward.

or 'std::optional<Doodad> &std::optional<Doodad>::operator =(std::optional<_Ty2> &&) noexcept(<expr>)'
    'std::optional<Doodad> &std::optional<Doodad>::operator =(std::optional<_Ty2> &&) noexcept(<expr>)': could not deduce template argument for 'std::optional<_Ty2> &&' from 'Doodad'

This is the case of move-assigning a std::optional<T2> to a std::optional<T1>. This is also not what we were trying to do, so the fact that it failed is expected.

Keep going.

or 'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<_Ty2> &) noexcept(<expr>)'
    'std::optional<Doodad> &std::optional<Doodad>::operator =(const std::optional<_Ty2> &) noexcept(<expr>)': could not deduce template argument for 'const std::optional<_Ty2> &' from 'Doodad'

This is the copy-assignment version of the above, so we can skip this one, too.

or 'std::optional<Doodad> &std::optional<Doodad>::operator =(_Ty2 &&) noexcept(<expr>)'
    'std::optional<Doodad> &std::optional<Doodad>::operator =(_Ty2 &&) noexcept(<expr>)': could not deduce template argument for '__formal'
        'std::enable_if_t<false,int>' : Failed to specialize alias template
    while trying to match the argument list '(std::optional<Doodad>, Doodad)'

This is the final catch-all case of assigning an arbitrary object to an optional. This is the one we were hoping to use, but somehow it failed because of a “could not deduce template argument” from std::enable_if_t<false, int>, and that leading false tells us that an enable_if precondition failed. Let’s look at the precondition.

template <class _Ty2 = _Ty,
        enable_if_t<
            conjunction_v<
                negation<
                    is_same<optional, _Remove_cvref_t<_Ty2>>
                >,
                negation<
                    conjunction<is_scalar<_Ty>, is_same<_Ty, decay_t<_Ty2>>>
                >,
                is_constructible<_Ty, _Ty2>,
                is_assignable<_Ty&, _Ty2>
            >,
            int> = 0>
    _CONSTEXPR20 optional& operator=(_Ty2&& _Right) noexcept(⟦...⟧)

Let’s work on simplifying this template metaprogramming. In our case, _Ty2 is Doodad&, so std::decay_t<_Ty2> is std::decay_t<Doodad&>, which is Doodad. From its name, it’s highly likely that the internal template _Remove_cvref_t is std::remove_cv_t+std::remove_reference_t, but if you don’t trust your intuition, you can look it up for yourself:

template<class _Ty>
using _Remove_Cvref_t _MSVC_KNOWN_SEMANTICS = remove_cv_t<remove_reference_t<_Ty>>;

Applying it to the case where _Ty2 is Doodad& results in remove_cv_t<remove_reference_t<Doodad&>> which is remove_cv_t<Doodad> which is just Doodad. Plugging all that back into the enable_if, as well as _Ty = Doodad (since _Ty is the template parameter to optional itself) gives us this:

        enable_if_t<
            conjunction_v<
                negation<
                    is_same<optional, Doodad>
                >,
                negation<
                    conjunction<is_scalar<Doodad>, is_same<Doodad, Doodad>>
                >,
                is_constructible<Doodad, Doodad&>,
                is_assignable<Doodad&, Doodad&>
            >,
            int> = 0>
    _CONSTEXPR20 optional& operator=(Doodad;& _Right) noexcept(⟦...⟧)

Now we can interpret the expression. The operator is enabled if…

    !is_same<optional, Doodad> &&
    !(is_scalar<Doodad> && is_same<Doodad, Doodad>) &&
    is_constructible<Doodad, Doodad&> &&
    is_assignable<Doodad&, Doodad&>

(It so happens that these are precisely the conditions spelled out in the C++ language specification. I doubt this is a coincidence.)

The first clause says “you are not assigning from a std::optional<Doodad>“, which is true. We are assigning from a Doodad. The purpose of this clause is to remove this overload from consideration in favor of the other overload that specifically is for optional-to-optional assignment.

The second clause says “you are not trying to assign a scalar that is the same type of the optional.” I think this is to remove this overload from consideration in favor of converting the source scalar to an optional<_Ty> and assigning that. Regardless, it doesn’t apply here, so we pass that test too.

The next test is to see whether a Doodad can be constructed from a Doodad&, and in the case of a Doodad, it turns out that this is not true because the Doodad contains a unique_ptr, which makes it non-copyable.

Okay, so we can fix that by using std::move to move the Doodad on the stack into the optional, right?

            Doodad d;
            m_doodad = std::move(d);
            // or even
            m_doodad = Doodad();

Unfortunately, this fails in basically the same way. But how can that be?

It’s because Doodad is not move-assignable, even though all of its members are movable!

The requirements for an implicitly-defined move-assignment operator are that the type have no user-declared copy constructors, move constructors, copy assignment operators, or destructors. Our Doodad has a destructor, so that removes the implicitly-defined move-assignment operator.

Bonus reading: Implicit Move Must Go.

So our Doodad is not movable, not copyable.

One solution is to make our Doodad movable. This means investigating the class invariants and verifying that memberwise std::move preserves them. This can get tricky if, for example, the Doodad allowed pointers to itself to escape. If you’ve done the analysis and confirmed that memberwise std::move is correct behavior, you can add

    Doodad(Doodad&&) = default;
    Doodad& operator=(Doodad&&) = default;

to ask for the compiler to generate a default move constructor and default move assignment operator.

But maybe you study the Doodad and conclude that it is not movable for whatever reason. What else can you do?

We’ll look at our options next time.

The post The puzzle of trying to put an object into a <CODE>std::optional</CODE> appeared first on The Old New Thing.

Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete

Introducing the .NET Aspire Community Toolkit - .NET Blog

1 Share
Learn about the .NET Aspire Community Toolkit, a collection of tools and libraries that help you build with .NET Aspire.
Read the whole story
alvinashcraft
4 hours ago
reply
West Grove, PA
Share this story
Delete
Next Page of Stories