Steffen_actors_parents_children_working_hard_camera_cartoon_sty_69608d69-e3b6-498f-834a-eeb9a2fddf72.png

One of the talks on day 2 of SDD 2023, was called "Let's stop programming like 2017". The talk was held by Richard Blewitt (@RichardBlewitt) and Andrew Clymer (@AndrewClymer) from https://www.rocksolidknowledge.com/. I've been to a few talks with them and they are an absolute blast. Always lots of good laughs - just the way I like it!

In the talk they went through some of the improvements that has been made over the last years in C#. I will not go through all of them, but one really stood out to me: Pattern matching

In the bustling world of code, finding specific properties can be as challenging as spotting Waldo in a crowded scene. But fear not! C# 11's property pattern matching saves the day! It's a stylish and powerful feature that lets you uncover the hidden treasures of your objects with ease. So get out your magnifying glass and get ready to unravel the secrets of this exciting feature, transforming the search for Waldo (or any property) into a stylish and enjoyable code adventure.

Property pattern matching

One of the types of pattern matching that we can do is called property pattern matching. Property pattern matching in C# 11 is a feature that allows you to match against specific properties within objects. It provides a concise and expressive syntax for performing pattern matching based on the values of object properties.

With property pattern matching, you can easily extract relevant information or perform specific actions based on the properties of an object. It simplifies complex conditional logic by enabling you to match against property values directly within patterns.

The syntax for property pattern matching involves specifying the property name and its expected value in a pattern. This feature is particularly useful when dealing with objects that have multiple properties and you need to selectively handle different combinations of property values. It enhances code readability and conciseness, making it easier to write and understand complex matching scenarios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public string GetGreeting(Person person)
{
    return person switch
    {
        { Name: "Alice" } => "Hello, Alice!",
        { Name: "Bob", Age: 25 } => "Hey Bob, you're 25!",
        { Age: >= 18 } => "Welcome, adult!",
        _ => "Greetings!"
    };
}

In the example above, we have a `Person` class with properties `Name` and `Age`. The `GetGreeting` method takes a `Person` object and returns a greeting based on different matching patterns.

In the first pattern, `{ Name: "Alice" }`, we match against a `Person` object with the name "Alice". If the `Name` property matches "Alice", the corresponding greeting "Hello, Alice!" is returned.

In the second pattern, `{ Name: "Bob", Age: 25 }`, we match against a `Person` object with the name "Bob" and age 25. If both properties match the specified values, the greeting "Hey Bob, you're 25!" is returned.

The third pattern, `{ Age: >= 18 }`, matches any `Person` object where the `Age` property is greater than or equal to 18. If the age condition is met, the greeting "Welcome, adult!" is returned.

Finally, the underscore `_` acts as a catch-all pattern that matches any `Person` object not matched by the previous patterns. In this case, the generic greeting "Greetings!" is returned.

Property pattern matching allows for concise and expressive code by directly matching against object properties within patterns, making it easier to handle complex conditional logic and extract values from objects.

Type pattern matching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public string GetShapeInfo(object shape)
{
    return shape switch
    {
        Circle c => $"Circle with radius {c.Radius}",
        Rectangle r => $"Rectangle with width {r.Width} and height {r.Height}",
        _ => "Unknown shape"
    };
}

public class Circle
{
    public double Radius { get; set; }
}

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}

In the example above, we have a GetShapeInfo method that takes an object representing a shape. We use type pattern matching within the switch statement to determine the type of the shape and provide specific information based on that type.

When the shape matches the Circle type, we can access its properties directly within the pattern and return a formatted string with the radius value.

Similarly, when the shape matches the Rectangle type, we can access its properties and return a formatted string with the width and height values.

The underscore _ acts as the default pattern and matches any object that doesn't match the previous patterns. In this case, it returns "Unknown shape".

Type pattern matching allows us to check and extract specific types from objects, making it easier to handle different cases and provide type-specific behavior or information.

SDD 2023 started out with a fullday workshop and I choose to attend Alan Holubs workshop on microservices. I have attended talks with Alan Holub before, and he is truly a great speaker. He is very good at getting the audience involved and he does have some opinions that can be a bit provocative. But I actually like the provocative opinions, because it really makes you take a step back and see the way we do things from another perspective and that is something that can be really rewarding in the long run.

So what is a microservice? 

  1. An application single, small and clear responsibility
    1.  A biproduct is that the code base is very small 
  2. Independently deployable
    1. Can be deployed without other applications needing to know and without other applications needing to be deployed simultaniously
  3. Decentralized
    1. Can be deployed anywhere and do not rely on other services or applications being deployed in the same datacenter or environment
    2. Cannot share a database with other applications or services
    3. Should enforce authentication and authorization
  4. Fully autonomous
    1. Can handle faults and spike loads. Will not let faults ripple to other services
  5. Highly observable
    1. There is a need for good logging and monitoring of a microservice in order for the team to react, analyze and solve problems very fast

One of the main differences from the monolithic architecture, is that microservices are completely independent. That means that they do not share a database with other microservices. They might have a database of their own, to support the work that they are doing, but they will never have access to a database that is shared between other microservices. This is a quite a big change, when you compare to the way a monolithic application works, where each service in the monolith have access to the same database.  

"You want to avoid a situation where one change results in deployment of multiple services" - Allan Holub

Notify the tiny titans

All data that the microservice need must be served when the microservice is invoked. This typically happens in one of two ways. Either the microservice has a subscriber that subscribes to messages on an event bus (ie. RabbitMQ, Azure Service Bus, ZeroMQ) - this is the most normal and effective approach. Some microservices have a requirement to be able to receive requests from "the outside world" and have a REST interface. This approach should be held to a minimum due to the performance hit of HTTP requests.

Microservices are hard

Microservices is not a silver bullet though. The architecture has to be well planned and implemented with good attention to detail - if it's not, it can be even more complex than the good old monolith architecture that we all (most of us anyway) are trying to get away from. But if you implement it with a solid plan and attention to detail, microservices can be a very liberating architecture to work with. But it is very important to understand that creating a microservice architecture will introduce complexity and will require you to handle problems that is not prevalent in a classic monolithic architecture.

Tiny footprints for tiny titans

In order to mitigate the complexity that is introduced to the overall architecture, when you start to implement microservices, it is recommended to keep the implementation of the individual microservice as simple as possible. For instance if you need to temporarily store some data, then save it in a file using JSON. File read and writes are plenty fast and as long as it is not holding thounds of rows of data, a file can fulfill the need. Do not be tempted to add a database unless you absolutely need it. It will only add to the complexity of the overall solution, because you need to set up the pipeline to maintain the databaes (and you have to pay for it as well!).

Conclusion

In the vast software kingdom, microservices have emerged as the rulers, dethroning the monolithic giants of the past. Their agility, decentralization, and scalability have paved the way for a new era in software development. As we conclude this exploration, it's clear that the tiny titans of microservices will continue to shape the software landscape, offering new possibilities and unleashing the potential for innovation. Long live the reign of microservices in the software kingdom!