Modeling &  Building PayRoll Engine Using Functional Effects System With ZIO Asynchronous, Concurrent, ZIO Scala FP Library. HR Use case.

Modeling & Building PayRoll Engine Using Functional Effects System With ZIO Asynchronous, Concurrent, ZIO Scala FP Library. HR Use case.

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:

  • R?— the environment/dependency of our effect
  • E?— the type of errors that our effect may throw an exception
  • A?— the return type of our effect

ZIO also provides other data types:

type Task[+A] = ZIO[Any, Throwable, A]        

The task is a ZIO effect that

  • It doesn’t require an environment to run even if you provide any it will still run
  • It can fail with a Throwable.
  • It can succeed with an A.

type UIO[+A] = ZIO[Any, Nothing, A]        

The UIO is a ZIO that

  • It doesn’t require an environment to run
  • It can’t fail
  • It can succeed with an A

type IO[+E, +A] = ZIO[Any, E, A]        

  • It doesn’t require an environment to run
  • It can fail with an E
  • It can succeed with an 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:

  • The system should be processed each employee's Payslips using each province's Candian Revenue Agency's income tax table bracket.
  • The system should be able to process millions of employee payslips in parallel in a very efficient way taking advantage of the multi-core processor of our system.
  • The system should be simple, concise, easy to reason about, testable and maintainable, composable, and easy to change due to business change 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

  • Earnings ( overtime, allowances, bonus, claims, etc)
  • Deduction (loan, Income taxes, CPP, Pension, Employment Insurance, etc)

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.

No alt text provided for this image

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

No alt text provided for this image

We will model and represent the tax table above using the following code snippet below.

No alt text provided for this image

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.

No alt text provided for this image

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.

No alt text provided for this image

Let us model our employee, payslip use case classes.

  1. Employee represents employee code, province, gross pay, and list of deductions and earnings.
  2. Payslip represents an employee's net pay, total deduction, total earnings, and list of deductions and earnings.

No alt text provided for this image

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.

No alt text provided for this image

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.

No alt text provided for this image

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.

No alt text provided for this image

Let's analyze our code snippet above :

  1. we pass in the list of tax brackets.
  2. we create a function value to evaluate if the employee income is within the range of the tax bracket.
  3. we use tail recursion to loop through the determine the tax rate for the employee income.

We define EmployeeSlip as a type alias that represents a tuple of employee and payslip.

No alt text provided for this image

ZIO Payroll calculation services implementation.

No alt text provided for this image

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 :

  1. We looking up province income tax bracket using employee province code
  2. We look up federal income tax brackets using the Federal tax code
  3. We compute federal income tax using our tax bracket rate
  4. We compute the provincial income tax rate.
  5. We compute our total earnings.
  6. We compute employee total deductions.
  7. We subtract total deduction from total earnings.
  8. We create payslips
  9. We yield to a tuple of PaySip and Employe in our for-comprehension.

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.

No alt text provided for this image

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.

No alt text provided for this image

Let's analyze the code snippet above:

  1. line 12 we feed the payroll engine Layer with the two services dependencies taxable layer and Logger Layer.
  2. line 15 we create a 500k random list of employees.
  3. line 17 we provide an application layer to our Payroll services at the edge of the application
  4. We execute the generate employees' payslips methods on the ZIO runtime system.

No alt text provided for this image

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.

要查看或添加评论,请登录

Oluwaseyi Otun的更多文章

社区洞察

其他会员也浏览了