Skip to content

Repository files navigation

LineCodeApi

Description

Yet another useless piece of software (at least it's not a TODO API :kekw:) to showcase my Go dev skills. LineCodeAPI provides an interface both Web (REST) and gRPC for generating and storing (in a database) Line Codes, a technique used to convert digital data in a pattern of analog signals. For instance, a Manchester line code represents logical zeros as a Rising Edge (-A to +A), and logical ones as a Falling Edge (+A to -A).

Index

  1. Get Started (EZ Docker Run)
  2. App Architecture
  3. Manchester Model
  4. Domain Logic CODEC
  5. Data Persistence with GORM
  6. Web REST API with net/http (Go 1.22+)
  7. gRPC Server and Protobuf
  8. Containerizing with Docker

1. Get Started

This app can be easily run with Docker. An "example-docker-compose.yaml" file is provided inside the repo, copy the file as "docker-compose.yaml" and start it with docker compose.

cp example-docker-compose.yaml docker-compose.yaml
docker compose up

Once the app is running, the Web Server will be listening at:

localhost:8080

With endpoints:

GET {{url}}/manchester
POST {{url}}/manchester/encoder

bodyExample = {
    "decoded": "0AE3",
    "decodedPulseWidth": 400,
    "unit": "us"
}
POST {{url}}/manchester/decoder

bodyExample = {
    "encoded": "-A+A+A-A+A-A+A-A+A-A+A-A+A-A-A+A",
    "encodedPulseWidth": 400,
    "unit": "us"
}

To test de gRPC API (listening on localhost:9000), I used "grpc-cli" for my linux env (Arch btw):

grpc_cli ls localhost:9000

grpc_cli call localhost:9000 ManchesterEncode "decoded: '00E0FF', decodedPulseWidth: 200, unit: 'us'"

grpc_cli call localhost:9000 ManchesterDecode "encoded: '-A+A+A-A+A-A+A-A+A-A+A-A+A-A-A+A', decodedPulseWidth: 200, unit: 'us'"

But any gRPC client should work.

2. App Architecture

This Software uses an Hexagonal Architecture with 3 concentric layers:

  • A core with domain logic and models (without dependencies)
  • An Application Layer with the "ports" and the use cases.
  • An Adapters Layer with all the "adapters" both driven an drivers!

Note: The domain logic is not connected through ports, it is injected (Dependency Injection) on the main func though.

Ports are implemented using Go interfaces, including de API port (not a Service but API on the wider definition) which is a "reversed port" required to connect the driver adapters to their respective use cases. Most ports are defined in the "ports.go" file but the gRPC port is auto-generated by Protobuf as a Service.

Adapters are Go structs that implement code with external dependencies, each one will be explained on further sections. All adapters should be instantiated in the app entrypoint (main file) and mounted with Dependency Injection.

3. Manchester Model

There are a lot of line codes, this app (at the moment) provides a Manchester CODEC with a data model represented by the following JSON example:

{
    "decoded": "AE01",
    "encoded": "+A-A-A+A+A-A-A+A+A-A+A-A+A-A-A+A-A+A-A+A-A+A-A+A-A+A-A+A-A+A+A-A",
    "decodedPulseWidth": 200,
    "encodedPulseWidth": 100,
    "unit": "ns"
}

Notice that:

  • Decoded is a string of a hex representation of binary data. It MUST be valid bytes!
  • Encoded is the Manchester encoded data as a sequence of signal amplitudes (+A or -A) represented with a string.
  • Both pulse widths represent the time duration of a signal pulse or digital data on a "clock" context.
  • Unit is the time unit in seconds of the pulse width.

4. Domain Logic CODEC

You may be wondering: Why would someone build such a useless API? (line codes are quite important but at Hardware lvl or Telecom)... Well idk, I was kinda out of ideas but hear me out, Go is LOVELY you can easily do low level stuff lik this CODEC with some powerful tools like hash maps!

Manchester

The idea behind Manchester encoding is pretty easy:

var in bool
in = getBit()
if in {
    return "+A-A"
} else {
    return "-A+A"
}

This works great on low level with perfect control of the CPU clock (or MicroController), as a Service maybe it is not the best idea to check every bit every time for every request!

Therefore, the app generates a byte (8 bit) grouping dictionary and stores it on RAM as a Go map. This is an efficient way to get the encoded pattern of a byte without looping, reducing time complexity from O(n) to O(1), and consequently (with a chain of bytes) from O(n²) to O(n).

The dictionary constructing algorithm basically takes a byte and loops through this byte, it also uses a byte mask with an AND operator to check if a bit is set or cleared and appends the Manchester signal to the output.

Decoding uses the same aproach but reverted!

5. Data Persistence with GORM

This Adapter uses GORM to communicate with a Postgres DataBase, on creation, a model migration is performed (Code First). Connection parameters are specified as OS Environmental Variables!

The driven adapter implements the database port interface and follows GORM's approach of passing model's pointers as arguments. The port is generic over the data model (DbPort[T]), so its methods are reusable between data models.

Persistence should improve the app efficiency by searching the line code before creating it, avoiding unnecessary usage of the core logic.

Environment Variables

All configuration is provided through OS environment variables:

Variable Description Default
DB_SERVER Postgres host (none)
DB_USER Postgres user (none)
DB_PASSWORD Postgres password (none)
DB_PORT Postgres port (none)
DB_NAME Postgres database name (none)
DB_SSL_MODE Postgres SSL mode (e.g. disable) (none)
DB_TIME_ZONE Postgres time zone (e.g. UTC) (none)
WEB_PORT Port for the Web (REST) server 8080
GRPC_PORT Port for the gRPC server 9000

The DB variables are required; WEB_PORT and GRPC_PORT are optional and fall back to their defaults when unset.

6. Web REST API with net/http (Go 1.22+)

This is a driver Adapter, it runs a Web Server using the standard library's net/http package and the application api port. The requests are handled with the native ServeMux (leveraging Go 1.22+ routing enhancements). The listening port is configurable through the WEB_PORT env var (default 8080).

Some notes:

  • The adapter can be run with a go routine using the RunAsync Method, the main func should pass a context and a wait group to sync the execution!
  • The server shuts down gracefully when the context is cancelled (on SIGINT/SIGTERM), draining in-flight requests with a 10s timeout.
  • A simple Postman collection is provided inside the repo with some examples of requests.
  • Middleware can be added using standard http.Handler wrappers.

7. gRPC Server and Protobuf

This is also a driver Adapter that runs a server, it uses Protobuf for go in order to generate the service code. The "linecode_svc.proto" file contains both service and message types. The listening port is configurable through the GRPC_PORT env var (default 9000).

Some notes:

  • The service interface is auto-generated and passed to the adapter through the "UnimplementedLineCoderServer" (also auto-generated).
  • Reflection is activated for testing with grpc-cli!
  • The adapter can be run async (needed if you use several servers) with the RunAsync method, just like the REST API.
  • It also shuts down gracefully (via GracefulStop) when the context is cancelled, finishing in-flight RPCs first.

8. Containerizing with Docker

A Dockerfile is included, inside:

  • A Go with Alpine Linux image is pulled
  • Source files are copied and dependencies are re-downloaded
  • The app is built and the binary is properly relocated
  • The app is executed through a simple entrypoint script that delays the init some seconds.

An example of "docker-compose.yaml" is also included, inside:

  • A postgres DB image is pulled and run.
  • The app container is built with OS env vars and link dependencies.
  • A PGAdmin image is pulled and run for DB testing (this can be deleted).

About

Yet another useless piece of software (at least it's not a TODO API :kekw:) to showcase my Go dev skills. It uses some GORM, Gin, gRPC, Postgres, Hex Architecture, low level development.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages