Today I was working with credit cards and I needed to convert a 2-digit year to a 4-digit one in C#. The first thing that came to my mind was adding 2000 to it. But it didn’t feel right. It wouldn’t be a problem in hundreds of years, though.
To convert 2-digit year into a 4-digit year, use the ToFourDigitYear method inside your current culture’s calendar.
But, if you’re working with a string containing a date, create a custom CultureInfo instance and set the maximum year to 2099. After that, parse the string holding the date with the custom culture. Et voilà!
CultureInfoculture=newCultureInfo("en-US");culture.Calendar.TwoDigitYearMax=2099;stringdateString="1 Jan 21";DateTime.TryParse(dateString,culture,DateTimeStyles.None,outvarresult);// true, 1/1/2021 12:00:00 AM
These days, I use Moq a lot. There are things I like and I don’t like about creating fakes with Moq. But it’s simple and easy to use.
I use the same four Moq methods all the time. Setup, ReturnAsync, ThrowsAsync and Verify. That’s all you need. I decided to create snippets inside Visual Studio to avoid typing the same method names every time. These are the snippets I use.
Create a Mock with Moq
Use mn to create a Mock. It expands to var mock = new Mock<>();.
Use mn to create a Mock
Setup and Return
With a mock instance, use mr to Setup a method to Return something.
Use mr to Setup/Return
Setup and ThrowsException
If you want to throw an exception from your mock, use mt.
Use mt to Setup/ThrowsException
Also, you can use mra and mta for the asynchronous version of Return and ThrowsException respectively.
If you want to use the same snippets I use, download the snippets file from VSMoqSnippets repository.
To load snippets into Visual Studio, from the “Tools” menu, choose “Code Snippets Manager” and import the snippets file.
I’ve been working with Stripe to take payments. Depending on the volume of requests you make to the Stripe API, you might exceed the maximum number of requests per second. This is how we can implement a retry mechanism using the Decorator pattern in C#.
A Decorator wraps another object to extend its responsabilities, without modifying its existing behavior, while keeping the same signature of public methods. Decorators are used to add orthogonal responsibilities like logging, caching and retrying.
Let’s use Marvel movies to understand the Decorator pattern. When IronMan wore the HULKBUSTER suit in the Age of Ultron, he implemented the Decorator pattern. He had a new functionality, stopping the Hulk, while keeping his same functions, being IronMan. I hope you got it!
When you wear a jacket, you use the Decorator pattern too. Photo by Archie on Unsplash
Naive Retry logic
Let’s start with a PaymentService. This service collects everything it needs to start a payment with Stripe. For example, customer id, fees, destination account, etc. Then, it uses a PaymentIntentService to call Stripe API using its C# client.
This would be the CreatePaymentIntentAsync() method inside the PaymentService.
The RetryAsync() helper method will execute the API call a fixed number of times if it fails with a TooManyRequests status code. If it fails with a different exception or status code, it propagates the exception to the caller.
This is a simple implementation of a retry method.
protectedasyncTask<TResult>RetryAsync<TResult>(Func<Task<TResult>>apiCommand,intmaxRetryCount=3){varexceptions=newList<Exception>();varretryCount=0;while(true){try{returnawaitapiCommand();}catch(StripeExceptionex)when(ex.HttpStatusCode==HttpStatusCode.TooManyRequests){exceptions.Add(ex);if(retryCount==retryCountMax){thrownewAggregateException("Too many requests",exceptions);}retryCount++;}}}
Later, we can replace this helper method with a more robust implementation using Polly, for example. It can include incremental delays between failed attempts and timeouts.
But, using this helper method implies wrapping the methods to retry inside our helper method all over our codebase. Hopefully, if we have a singe place to take payments, that wouldn’t be a problem. Also, our PaymentService mixes business logic with retry logic. That’s smelly. We should keep responsabilities separated.
Retry logic with Decorator pattern: Let’s Decorate
For a more clean solution, let’s use the Decorator pattern.
First, let’s create a decorator called RetryablePaymentIntentService for the PaymentIntentService. Since we want to keep the same API of public methods, the decorator should inherit from the same interface, IPaymentIntentService.
publicclassRetryablePaymentIntentService:IPaymentIntentService{publicTask<PaymentIntent>CreateAsync(PaymentIntentCreateOptionsoptions,RequestOptionsrequestOptions=null,CancellationTokencancellationToken=default){// We will fill the details in the next steps}}
The decorator will only handle the retry logic. It will use the existing PaymentIntentService to call Stripe. The decorator will receive another IPaymentIntentService in its constructor.
publicclassRetryablePaymentIntentService:IPaymentIntentService{privatereadonlyIPaymentIntentService_decorated;publicRetryablePaymentIntentService(IPaymentIntentServicedecorated){_decorated=decorated;}publicTask<PaymentIntent>CreateAsync(PaymentIntentCreateOptionsoptions,RequestOptionsrequestOptions=null,CancellationTokencancellationToken=default){// We will fill the details in the next steps}}
Notice, we named the field in the decorator, _decorated. And, yes, the decorator inherits and receives the same type. That’s the trick!
Next, we need to fill in the details. To complete our decorator, let’s use our previous RetryAsync() method. Our decorator will look like this,
publicclassRetryablePaymentIntentService:PaymentIntentService{privatereadonlyPaymentIntentService_decorated;publicRetryablePaymentIntentService(PaymentIntentServicedecorated){_decorated=decorated;}publicTask<PaymentIntent>CreateAsync(PaymentIntentCreateOptionsoptions,RequestOptionsrequestOptions=null,CancellationTokencancellationToken=default){returnRetryAsync(async()=>{returnawait_decorated.CreateAsync(paymentIntentOptions,requestOptions,cancellationToken);});}// Same RetryAsync method as before...}
Now, our decorator is ready to use it. In the PaymentService, we can replace the simple PaymentIntentService by our new RetryablePaymentIntentService. Both services implement the same interface.
We can create our decorator like this,
newRetryablePaymentIntentService(newPaymentIntentService(/* Other dependencies */));
Inject Decorators into ASP.NET Core container
Let’s register our decorator
But, if you’re using an ASP.NET Core API project, we can use the dependency container to build the decorator.
Let’s use an extension method AddPaymentServices() to group the registration of our services. You can register your services directly into the Startup class. No problem!
This time, we registered the original PaymentIntentService without specifying an interface. We only used the IPaymentIntentService to register the decorator. When resolved, the PaymentService will receive the decorator instead of the original service without retry logic.
Let’s use Scrutor to register our decorator
Optionally, we can use Scrutor to register the decorated version of the IPaymentIntentService. Scrutor is a library that adds more features to the built-in dependencies container. Don’t forget to install the Scrutor NuGet package into your project, if you choose this route.
In that case, our AddPaymentServices() will look like this,
publicstaticclassServiceCollectionExtensions{publicstaticvoidAddPaymentServices(thisIServiceCollectionservices){services.AddTransient<IPaymentIntentService,PaymentIntentService>();// With Scrutor, we need the method Decorateservices.Decorate<IPaymentIntentService,RetryablePaymentIntentService>();services.AddTransient<IPaymentService,PaymentService>();}}
Notice, this time we have explicitly register two entries for the IPaymentIntentService. The Decorate() method does the trick for us.
Voilà! That’s how we can implement the Decorator pattern to retry API calls. We can also use the decorator pattern to bring logging or caching to our services. Check how you can use the Decorator pattern to add a Redis caching layer with ASP.NET Core.
For more real-world examples, check my post on Primitive Obsession. That’s about handling Stripe currency units. If you want my take on another pattern, check my post about the Pipeline pattern.
A passing test isn’t always the only thing to look for. It’s important to see our test failing. I learned this lesson the hard way. Let’s see why we should start writing failing tests.
To write reliable unit tests, always start writing a failing test. And make sure it fails for the right reasons. Follow the Red, Green, Refactor principle of Test-Driven Development (TDD). Write a failing test, make it pass, and refactor the code. Don’t skip the failing test part.
From our last example, we had a controller to create, update, and suspend user accounts. Inside its constructor, this controller validated some email addresses from an injected configuration object.
After we refactored our test from the last post, we ended up with this:
This time, I had a new requirement. I needed to add a new method to our AccountController. This new method read another configuration object injected into the controller.
To follow the convention of validating required parameters inside constructors, I also checked for this new configuration object. I wrote a new test and a new MakeAccountController() builder method to call the constructor with only the parameters I needed.
[TestMethod]publicvoidAccountController_NoNewConfig_ThrowsException(){varoptions=Options.Create<SomeNewConfig>(null);Assert.ThrowsException<ArgumentNullException>(()=>MakeAccountController(options));}// A new builder methodprivateAccountControllerMakeAccountController(IOptions<SomeNewConfig>someNewConfig){varemailConfig=newMock<IOptions<EmailConfiguration>());returnCreateAccountController(emailConfig.Object,someNewConfig);}privateAccountControllerMakeAccountController(IOptions<EmailConfiguration>emailConfig,IOptions<SomeNewConfig>someNewConfig){// It calls the constructor with mocks, except for emailConfig and someNewConfig}
I ran the test and it passed. Move on! But…Wait! There’s something wrong with that test! Did you spot the issue?
Make your tests fail for the right reasons
Of course, that test is passing. The code throws an ArgumentNullException. But, that exception is coming from the wrong place. It comes from the validation for the email configuration, not from our new validation.
I forgot to use a valid email configuration in the new MakeAccountController() builder method. I used a mock reference without setting up any values. I only realized that after getting my code reviewed. Point for the code review!
privateAccountControllerMakeAccountController(IOptions<SomeNewConfig>someNewConfig){varemailConfig=newMock<IOptions<EmailConfiguration>());// ^^^^^// Here we need to setup a valid EmailConfigurationreturnCreateAccountController(emailConfig.Object,someNewConfig);}
Let’s make sure to start always by writing a failing test. And, this test should fail for the right reasons.
If we write our tests after writing our production code, let’s comment some parts of our production code to see if our tests fail or change the assertions on purpose.
When we make a failed test pass, we’re testing the test. We’re making sure it fails and passes when it should. We know we aren’t writing buggy tests or introducing false positives into our test suite.
A better test for our example would check the exception message. Like this:
Voilà! This task reminded me to see my tests fail for the right reasons. Do you have passing tests? Do they pass and fail when they should? I hope they do after reading this post.
For more tips on how to write good unit tests, check my follow-up on using simple test values. Don’t miss the rest of my Unit Testing 101 series where I also cover mocking, assertions, and best practices.
These days, I watched a conference online by Sarah Mei (@sarahmei) titled: Livable Code. This is what livable code is.
Livable Code is the analogy that building software is like living an space inside a building instead of constructing a house or a building.
Building software isn’t like building anything else. It isn’t like planning, constructing and maintaining a tower of apartments. It’s living an space where each decision counts towards having a mess or a enjoyable place to live.
Code hoarders
We don’t end up with a messy unlivable code because of a bad choice of frameworks or technologies. Of course, the shape of the space will influence the design of our house. But, messy code comes after small decisions made every day. Patch after patch. One day you receive a package and throw the box and the plastic bags in a corner. The next thing you know is that pile almost reaches the ceiling.
Messy code comes after small decisions made every day. Photo by Lance Grandahl on Unsplash
Everyday Decisions and Perfect Clean Code
We have to live with every decision we have made inside our code. Instead of, building something and moving on, we live with software. We don’t have “done” projects anymore. We don’t ship software into burned CD’s and hand the maintenance to somebody else. We have to get to live with our code and make it livable for the ones around us.
There is no perfect clean code. In fact, it’s unattainable. Perfect clean code only exists on books and blog posts. The same as perfectly design minimalist spaces in magazines. We need certain amount of stuff to live. A pile of books, a video game console. An amount of stuff we can tolerate. We could have spaghetti code that nobody understands or so clean and perfectly crafted code that nobody understands either. Both extremes are unlivable.
4 rules for more livable code
The author in the conference gives 4 rules to make your code more livable.
Don’t make it worse. No matter what you have to do, don’t make it worse. Remind this to other team members when needed.
Improvement over consistency. Ship your technical debt away, one change at a time.
Inline everyday. Don’t have stories for big refactors. Make refactoring an everyday job. Always leave the campground cleaner than you found it.
Liase. Communicate, don’t lie. Treat refactoring as everything you do.
Don’t ask for permission. But, be upfront.
Don’t ask for forgiveness. But, learn every time.
Ask for advice. But, don’t always take it.
Work together. We all have to live here!
The most important part of software is not the code, or the people. It’s the system.