Tutorial: Defining the Domain entities

Tutorial: Defining the Domain entities

This blog is the part of the series Building a Web App with Golang.

In today's article we are going to define the basic entities and the operations on them for our web application. In the first part we defined the scope of the application.

We are going to build a system that can issue issue invoices for clients.

Let's create a new git branch to work on:

git checkout -b basic-domain


Every client and "us" (our company/the freelancer) will be represented by an entity Company .

Let's create a file named company.go in the root of our project

then we define a struct with the basic attributes we have:

package invoicehub

type Company struct {
    ID           int
    Name         string
    Address      Address
    Email        string
    TaxID        string
    VatID        string
    BankAccounts []BankAccount

type Address struct {
    Street     string
    City       string
    PostalCode string
    Country    string

type BankAccount struct {
    BankName      string
    AccountNumber string
    IBAN          string
    BIC           string

The idea is that we will represent each real life company using the above struct.


Create an invoice.go in the root of the project

Each invoice will have at least the following:
- Seller
- Buyer
- Amount
- Issue Date
- Invoice Number
- Days (number of days after issuance that must be paid).

We need to install first one dependency to handle decimal numbers:

get github.com/shopspring/decimal

Now let's start with that:

package invoicehub

import (


type Invoice struct {
    ID            int
    InvoiceNumber string
    IssueDate     time.Time
    SellerID      int
    BuyerID       int
    DaysToPay     int

    LineItems []LineItem

type LineItem struct {
    Description string
    Amount      Amount
    VatRate     decimal.Decimal

type Amount struct {
    Value    decimal.Decimal
    Currency string

The above structs should satisfy our needs for now.

Pay, attention that I am using ids for Buyer and Seller. We could have used a Company struct there.

Why I choose to use ids instead of a company struct?

Our application now is a "monolithic" application. In the future we might want to split into different microservices (maybe one microservice to handle the companies and one microservice to handle the invoices). Let's be proactive and try to decouple the Companies with the Invoices.

I does not look that we need other structs to represent our business domain. Of-course , we might forgotten something but then we will change.


Our application is pretty simple:

For a Company we need to be able to:

Create a company
Read a company
Update a company

Our requirements don't have a Delete so we skip for now.

Creating, Reading, Updating a company are straightforward.

First we need a way to do these operations in our storage layer.
For this we are going to create an interface CompanyRepository

Let's do that:

in your company.go add the following:

type CompanyRepository interface {
    Create(ctx context.Context, company *Company) (int, error)
    Update(ctx context.Context, company *Company) error
    Get(ctx context.Context, id int) (Company, error)

Now for our invoices:

We need to be able to :

Create Invoice
Read Invoice
Create a PDF from an invoice

we don't have full CRUD (Create Read Update Delete) since we don't want to Update or Delete invoices.

Let's define the repository also for invoices:

in the invoice.go add:

type InvoiceRepository interface {
    Create(ctx context.Context, invoice *Invoice) (int, error)
    Get(ctx context.Context, id int) (Invoice, error)

What about PDF generation here? This is not a database operation.
Additionally, what about creation of an invoice? It is a little bit more complex.

In the requirements we had that the InvoiceNumber looks like:


so we have two parts:
1. a number that resets every year (1031)
2. the year (24)

So the next invoice should look like 1032/24 but if it is the first for 2025 should look like 1001/24 .

Notice that we start always with 1000.

This is just one way others, might do it differently.

It would be nice to have a generic way to compute that per customer. BUT, do not forget our requirements. We want to code for now and not for imaginary use cases. Let's get stuff done

Since invoices are a bit more complex let's also create a Service Layer that will handle the business logic .

open invoices.go and add:

type InvoiceService interface {
    Create(ctx context.Context, invoice *Invoice) error
    Get(ctx context.Context, id int) (Invoice, error)
    CreatePDF(ctx context.Context, id int) ([]byte, error)

We also need an extra method in the Repository that returns the last invoice of the year. We are going to use that to compute the invoice number

Add the GetLastInvoiceForYear in the repository

Adding the interfaces in our HTTP layer

Now that we have "described" the operations of our system via the interfaces let's see where we are going to use them.

in the http/router.go we defined a base handler :

Let's add the interfaces there:

type baseHandler struct {
    e         *echo.Echo
    companies invoicehub.CompanyRepository
    invoices  invoicehub.InvoiceService

so when we create the baseHandler we should inject the implementations of the interfaces:

Since we don't have any concrete implementation yet let's pass nil where we call the NewRouter function in our cmd/main.go .

Commit and Push

let's commit our code and push our branch

git add .
git commit -m "add basic domain entities"
git push origin basic-domain

And as usual, you can find today's code in the github branch

Summary and what is next

In today's blog post we started implementing our business logic. We first defined the required structs and the operations that we can do on these.
The operations are defined using interfaces. Utilizing interfaces allows us to think about our business logic without having to think about implementation details. Additionally, it will allow us to easily unit test our application later.

I would like to share a piece of advice here. When you have to develop an application no matter how complex it is try first to focus on what is your goal. Don't fall into the trap of questioning your self all the time like:

what if the user wants also that?
what if this changes ?
what if ... ?

These what if may never happen. If they do be confident that when it's required you will handle it.

I am not saying or implying do not think about scalability, changes of requirements etc . I am saying think about them try to code in a way that some decisions are deferred but don't waste your time trying to code something smart when it is not required.

The moments I am writing these lines I am thinking the following:

- How am I distinguish which company is the Seller when we don't have a User entity ?
- What if when we create an invoice someone else does it parallel (so the invoice number may come wrong?_
- How am i going to create an invoice number when I do it near the end of the year and I use UTC in the system but my current timezone is UTC+3 and I am in the next year?
- ...

and many many more.

Some of these are maybe valid concerns. But so far we don't have anything working yet. We don't have to aim for perfection, a working software is better that no software in the end.

Links to the other part of that series





👍 Please leave a comment or like the article if you like it. This will help me gain visibility.

❤️ Following me on Github or X or LinkedIn motivates me keep writing.