Railway-oriented programming (ROP)

Railway-oriented programming (ROP) is a functional programming approach to handling errors and data flow. It uses the analogy of a railway track to represent the possible outcomes of a function:

Similar to how a train can only be on one track at a time, an ROP function can only return either a success or a failure. This enforces clarity and simplifies error handling in your code.

Getting started with ROP

Not all methods force you to throw exceptions. Exceptions are a better choice when you have unexpected behavior in your application. For expected cases, it is better to apply a functional programming technique called Railway-oriented programming.

Well, that is what I think about exceptions:

What went wrong?

Having exceptions makes your code dishonest. The dishonest code requires more investigation, it is hard to understand it at first look, and it doesn’t provide a “valid” understanding when you read the signature of the method. I always prefer honest signatures for my methods. Because it makes my code more understandable, readable and requires less documentation; well, at least, it doesn’t force you to read documentation whenever you are faced with a new method.

PS: Download our repo to follow the examples: GitHub - TuralSuleymani/DecodeBytes at tutorial/result_pattern

Here is our code

using Common.Repository;
using Domain.Models;
using Repository.Abstraction;
using Service.Abstaction;
using System;
using System.Threading.Tasks;

namespace Service.Core
{
    public class AccountService : IAccountService
    {
        private readonly ICustomerService _customerService;
        private readonly IUnitOfWork _unitOfWork;
        private readonly IAccountRepository _accountRepository;

        public AccountService(
            ICustomerService customerService,
            IAccountRepository accountRepository,
            IUnitOfWork unitOfWork)
        {
            _customerService = customerService;
            _unitOfWork = unitOfWork;
            _accountRepository = accountRepository;
        }

        public async Task<Account> AddAsync(Account account)
        {
            bool isCustomerExist = await _customerService.IsExistsAsync(account.CustomerId);

            if (!isCustomerExist)
            {
                throw new Exception("Customer with given Id doesn't exist");
            }
            else
            {
                bool isAccountExist = await _accountRepository.IsExistsAsync(account.AccountNumber);

                if (isAccountExist)
                {
                    await _unitOfWork.AddAsync(account);
                    await _unitOfWork.SaveChangesAsync();
                    return account;
                }
                else
                {
                    throw new Exception("Account with given number doesn't exist");
                }
            }
        }
    }
}

What do you think about the code above?

Everything seems ok but when it comes to reading and understanding the code from the signature it is really hard to understand what this method is planning to return:

async Task<Account> AddAsync(Account account)

Is it always Account?

After delving into the source code, we see that it may return some exceptions depending on the values. It makes our signature dishonest. To work further with this method, we need to wrap it into try..catch and handle exceptions properly.

But how to deal with it?

One of the best choices to work with such a type of code snippet is to rewrite it and adopt a functional programming technique called Railway-oriented programming.

Railway Oriented Programming (ROP) is a technique for handling errors in C# that borrows ideas from functional programming. Here are some benefits of using ROP in C# applications:

Note. It's important that ROP isn't a silver bullet. Here are some things to consider:

Conclusion

Overall, ROP is a valuable tool for handling errors in C# applications, particularly when dealing with complex workflows or error-handling logic. If you're looking to improve code readability, and composability, and leverage some functional programming concepts, ROP is worth exploring.

In our next tutorial, we will implement the above code using the ROP Result pattern.