Modeling & Building PayRoll Engine Using Functional Effects System With ZIO Asynchronous, Concurrent, ZIO Scala FP Library. HR Use case.
Oluwaseyi Otun
Lead Backend Software Engineer ( Scala, Python, Java, Kafka, ZIO, Akka)
Scala function effect ecosystem is gaining popularity for building highly efficient, parallel, and concurrent effects systems, such as Cats Effect, Monix Task, and ZIO. Effect systems are highly optimized for speed, memory efficiency. resource management and building highly concurrent applications.
In this article, we’ll attempt to build a highly efficient Payroll Calculation Engine using the ZIO scala pure functional effect library. Understanding the effects system using the Cats Effects library can be challenging because it brings the concept of mathematical category theory into the picture. Semigroup, Monoid, Applicative, Monad, etc. This concept can be really challenging to understand if you are not coming from a mathematical background.
ZIO demystify that for us and hides all the mathematical jargon that makes a lot of developers run away from scala pure functional programming language for building a modern application that solves the business problem with speed and efficiency. ZIO has interoperability with the Cat Effects library.
This article assumes the reader has a fair knowledge of pure functional programming in Scala but don't worry I am going to break it down using domain modeling technique and a use-case that can be reason about easily even Java and OOP developers come along.
What is an Effect System
Effect systems allow describing effects (non-pure operations such as IO operations connecting to the database, open socket connections, logging messages to the console, etc) as values that can be easily and safely composed into descriptions of bigger programs. It allows describing asynchrony and concurrency using?Fibers, which can be thought of as lightweight (green) threads. The description is then executed (or run) on the ZIO runtime system at the edge of the program aka (End of the world).
What is a Functional Effect System
The functional effect is about turning the computation into first-class values.?An effect can be thought of as an operation itself, but a functional effect is a description of an operation.
What is ZIO
ZIO?is a zero-dependency library for?asynchronous and concurrent programming?in Scala. It is a?functional effect system?in Scala using non-blocking fibers green thread that never wastes or leaks resources, ZIO allows us to build highly concurrent, parallel, scalable, and resilient modern and cloud-native business applications.
ZIO Data Type
The?ZIO[R, E, A]?is a core data type around the ZIO library. We can think of the?ZIO?data type as a conceptual model like a function from?R?to?Either[E, A]
R => Either[E, A]
The function above, which requires an?R environment or dependencies the function's needs, might produce either an?E, representing failure, or an?A, representing success. ZIO effects are not actually doing something, because they model complex effects, like asynchronous and concurrent effects. The effects are passed to the ZIO runtime for execution at the edge of the program.
The?ZIO[R, E, A]?data type encodes three different things about our effect:
ZIO also provides other data types:
type Task[+A] = ZIO[Any, Throwable, A]
The task is a ZIO effect that
type UIO[+A] = ZIO[Any, Nothing, A]
The UIO is a ZIO that
type IO[+E, +A] = ZIO[Any, E, A]
Let's implement our Payroll engine that can process and calculate millions of employees' payslips using the parallel execution power by the ZIO Runtime system.
Let's understand our high-level business requirement for our payroll system.
Use Case
Lasgidi PayDay?is a fictitious Canadian Payroll and Human Resource startup that provides payroll services in Canada. They just raised Series A funding from investors. As a startup, they have adopted a functional effect system to rapidly build and scale their solution to millions of users across Canada. They want to take advantage of effects systems to build solutions that are very highly optimized for speed, memory & CPU efficiency, testable and composable, and maintainable and reason about by design. They have decided to go with?Scala, a pure functional programing language using functional effects library ZIO.
Below are the functional and non-functional requirements:
Payroll Overview In Canada
Payroll is the process where the employer calculates the wages and distributes them to the employees. The company withholds taxes, CPP, and EI contributions from the paycheque, and the remaining amount is paid to the employee. In Canada there are federal and provincial income taxes paid, while CPP is a contribution to the Canada Pension Plan, and EI is a contribution to the Employment Insurance program. The federal income tax deduction depends on the level of the annual income, and it ranges between 15% and 33%. The provincial income tax deduction also depends on the annual income, but it has different rates from province to province.
Payroll components can be broken down into
Let us derive a simple payroll calculation formula that we will use to implement our payroll engine.
NetPay = (Total Earnings - Total Deducation)
We represent our paySlip generation function using the type alias below. This serves as our building block as we know Scala is an expressive language.
The generatePayslip type alias function takes income tax bracket configuration and employee details as input and calculates an output tuple of employee and payslips.
Federal Provisional Tax Brackets And Rates
The taxable income is based on the tax bracket. Each bracket pays a different rate of tax, as the table below shows
We will model and represent the tax table above using the following code snippet below.
The taxable needs to be stored in a load from a storage location for simplicity we store as a JSON representation in the configuration file. We load and store as Tax Table configuration as key-value map the Province code as a Key while the Taxable bracket is the value.
Its configuration represents our ZIO environment or dependency.
Below is a code snippet of the TaxTable configuration using the ZIO type-safe config library.
领英推荐
Domain Modeling Using Algebraic Data Type
In scala, we use algebraic data types and immutability to model our domain. In our payroll domain, we deal with various types like claims, overtime, loan, basic and income tax, etc. These are various classifications but the base type is still deduction and earnings. We generally model ADT using case classes and seal traits in scala.
Let us model our employee, payslip use case classes.
Pure function heart of functional Programming
A pure function must be deterministic and must depend only on its inputs. This means that for each input that is provided to the function, the same output must be returned, no matter how many times the function is called. With pure function, testing is easier, changing business requirements and fearless code refactoring and code maintainability are possible.
Let's model our Payroll logic using a pure function in scala.
Earnings calculation function.
It is a function type alias that takes a list of earnings as input and the ZIO UIO data type as output. It uses the foldLeft combinator function to sum all the total earnings of an employee and we assign it as the function value.
Deductions calculation function.
It is a function type alias that takes a list of deductions as input and the ZIO UIO data type. It uses the foldLeft combinator function to sum all the total deductions of an employee.
Income Tax bracket pure functions.
Let's analyze our code snippet above :
We define EmployeeSlip as a type alias that represents a tuple of employee and payslip.
ZIO Payroll calculation services implementation.
In Scala,?for-comprehension?is nothing more than?syntactic sugar?to a sequence of calls to one or more of the methods. ZIO data type implements map and flatmap which are the two fundamental methods needed to use for-comprehension.
Let's analyze our code snippet above :
Parallel Execution Using Fibers
Fiber is a schedulable computation, just like JVM thread. However, is a data structure, which is scheduled for execution by ZIO runtime on the internal? JVM thread pool. Unlike a system/JVM thread which is expensive to start and stop, fibers are cheap to allocate and remove. We can create millions of fibers and switch between them without the overheads associated with threads.
ZLayer Data Type
ZLayer[-RIn, +E, +ROut]
ZLayer describes a layer of an application just like dependencies, every layer in an application requires some services (the input) and produces some services (the output). The creation of layers can be effectful and utilize resources that must be acquired and safely released when the services are done being utilized. Layers are shared by default, meaning that if the same layer is used twice the layer will only be allocated a single time.
We construct our layer using function composition using the ZLayer.
It takes two dependencies as inputs are Logger and TaxTableConfig and it produces PayDayCalculationService.
Let's run our payroll engine by providing application layers.
Let's analyze the code snippet above:
As we can see in the log above. ZIO runtime uses different fibers (zio-default-async-n) to execute our computation. We can process over 500k employee payslips in a very efficient using parallel execution in an ergonomics way. This is the power of ZIO.
The full source code can be downloaded on my Github repository. Feel free to clone the repo modify, play around, and enhance the source code adapt it to your use case.
Takeaways
In this article, we learned some important capabilities of the ZIO effect, which helps us to write high-performance and concurrent applications with ZIO fibers, making using the functional programming paradigm. ZIO is a great tool for building resilient and high-performance systems without worrying about deadlock, resource leaks, race conditions.
Our implementation is very concise and easy to reason about. With a few lines of code, we can process over 1million employee records in milliseconds. That is the power of ZIO and pure functional programming(FP). Stay tuned for more ZIO blogs.
Special thanks to?John De Goes for creating ZIO and other ZIO contributors and also for evangelizing pure functional programming in scala communities around the world.
Read more on ZIO Type-safe, composable asynchronous, and concurrent programming for Scala
Thank you for reading.
Oluwaseyi Otun?is an independent consultant focused on backend and data engineering (Scala, ZIO, Akka, Cats Effects, Kafka, Apache Spark, Java). Distributed system enthusiast and passionate and special interest in functional programming with a special interest in ZIO, software architecture, design pattern, microservice, clean and quality code. I love learning internals working on a system and exploring what happens under the cover.