rony

package module
v0.11.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2023 License: BSD-3-Clause Imports: 11 Imported by: 4

README

Rony

Rony is a framework for developing high-performance API servers. It is designed to be simple and flexible. It uses the power of Go generics to provide an easy and robust code and helps you to detect a good few of the common mistakes at compile time. If you need to have more control over your code, such as selecting your own Gateway and Cluster, you can use Kit Package however for most use cases we recommend to use rony package instead.

Installation

To install Rony package, you need to install Go and set your Go workspace first.

First need to installGo (version 1.19+ is required), then you can use the below Go command to install RonyKIT.

$ go get -u github.com/clubpay/ronykit/rony/...

Quick start

State

When you develop API servers, you usually have a common state which could be shared between a few of your endpoints (i.e., Contracts). For example, you may have a database connection or a cache connection which you want to share between your endpoints. Moreover, you may want to have a shared state like a counter of the requests you received or in a simple chat application to keep the list of the connected users. Rony lets you define your own state and provides it to your handlers, so you can access it from your handlers without relying on global variables or defining your handler functions as methods of a common structs. The later two approaches can be problematic as your project grows.

The following code shows the type parameter of the State that your need to implement for your server.

package rony

// State related types
type (
	Action          comparable
	State[A Action] interface {
		Name() string
		Reduce(action A)
	}
)

As you can see, the State is a generic type which has a type parameter named Action which is a comparable type. This is defined to let you define your state in a Reducer pattern. Also we recommend that your state also implements sync.Locker interface to be thread-safe.

Echo Program with State

Lets first implement our State. We want to have a simple counter which counts the number of requests we received. Our EchoCounter state has action type of string and it supports two actions: up and down. The up action increases the counter and the down action decreases the counter. The following code shows the implementation of the EchoCounter state.

package main

import (
	"sync"

	"github.com/clubpay/ronykit/rony"
)

type EchoCounter struct {
	sync.Mutex

	Count int
}

func (e *EchoCounter) Name() string {
	return "EchoCounter"
}

func (e *EchoCounter) Reduce(action string) {
	switch action {
	case "up":
		e.Count++
	case "down":
		e.Count--
	}
}

Now We want to implement our handlers to handle UP and DOWN functionality. We need to first define DTOs (Data Transfer Objects) of our handlers. However instead of defining two separate DTOs, we going to define one DTO and use the Action field of the DTO to determine the action we want to perform. Although, this is just in our example, in your case you may come up with a better design.

package main

import (
	"github.com/clubpay/ronykit/rony"
)

type CounterRequestDTO struct {
	Action string `json:"action"`
	Count int `json:"count"`
}

type CounterResponseDTO struct {
	Count int `json:"count"`
}

Now we need to define our handler. Let's first define our handler struct.

package main

func count(ctx *rony.UnaryCtx[*EchoCounter, string], req *CounterRequestDTO) (*CounterResponseDTO, error) {
	res := &CounterResponseDTO{}
	err := ctx.ReduceState(
		req.Action,
		func(s *EchoCounter) error {
			if s.Count < 0 {
				return rony.NewError(fmt.Errorf("count is negative: %d", s.Count)).SetCode(http.StatusBadRequest)
			}

			res.Count = s.Count

			return nil
		},
	)
	if err != nil {
		return nil, err
	}

	return res, nil
}

As you can see, we have a function named count which has two parameters. The first parameter is a UnaryCtx which is a generic type and provides the state to the handler. It also has many more helper methods which we will discuss later. The second parameter is the request DTO. The return value of the handler is the response DTO and an error. The handler function is a normal function and it is not a method of a struct. Inside the handler code, you see we have ReduceState method which is a helper method that let us to mutate the state in an atomic fashion. The code in the callback function of the ReduceState method is executed in a thread-safe manner and it is guaranteed that no other goroutine is mutating the state while the callback function is running.

Now let's wrap up the code and define our server.

package main

import (
	"context"
	"os"

	"github.com/clubpay/ronykit/rony"
)

func main() {
	srv := rony.NewServer(
		rony.Listen(":80"),
		rony.WithServerName("CounterServer"),
	)

	// Set up the server with the initial state, which is a pointer to EchoCounter
	// We can have as many states as we want. But each handler can only work with
	// one state. In other words, we cannot register one handler with two different
	// setup contexts.
	setupCtx := rony.Setup(
		srv,
		rony.ToInitiateState[*EchoCounter, string](
			&EchoCounter{
				Count: 0,
			},
		),
	)

	// Register the count handler for both GET /count and GET /count/{action}
	// This way all the following requests are valid:
	// 1. GET /count/up&count=1
	// 2. GET /count/down&count=2
	// 3. GET /count?action=up&count=1
	// 4. GET /count?action=down&count=2
	rony.RegisterUnary(
		setupCtx, count,
		rony.GET("/count/{action}"),
		rony.GET("/count"),
	)

	// Run the server in blocking mode
	err := srv.Run(
		context.Background(),
		os.Kill, os.Interrupt,
	)
	if err != nil {
		panic(err)
	}
}

We first create a new server and then we set up the server with the initial state. We can have as many states as we want. Then we register our handler with the server. We can register as many handlers as we want. Finally we run the server in blocking mode. You can also check examples to see more examples.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterStream

func RegisterStream[IN, OUT Message, S State[A], A Action](
	setupCtx *SetupContext[S, A],
	h StreamHandler[S, A, IN, OUT],
	opt ...StreamOption,
)

func RegisterUnary

func RegisterUnary[IN, OUT Message, S State[A], A Action](
	setupCtx *SetupContext[S, A],
	h UnaryHandler[S, A, IN, OUT],
	opt ...UnaryOption,
)

Types

type Action

type Action comparable

State related types

type CORSConfig

type CORSConfig = fasthttp.CORSConfig

type CompressionLevel

type CompressionLevel = fasthttp.CompressionLevel
const (
	CompressionLevelDisabled        CompressionLevel = -1
	CompressionLevelDefault         CompressionLevel = 0
	CompressionLevelBestSpeed       CompressionLevel = 1
	CompressionLevelBestCompression CompressionLevel = 2
)

Represents compression level that will be used in the middleware

type DecoderFunc

type DecoderFunc func(bag RESTParams, data []byte) (kit.Message, error)

type Error

type Error struct {
	// contains filtered or unexported fields
}

func NewError

func NewError(err error) Error

func (Error) Error

func (e Error) Error() string

func (Error) GetCode

func (e Error) GetCode() int

func (Error) GetItem

func (e Error) GetItem() string

func (Error) SetCode

func (e Error) SetCode(code int) Error

func (Error) SetItem

func (e Error) SetItem(item string) Error

type InitState

type InitState[S State[A], A Action] func() S

func ToInitiateState

func ToInitiateState[S State[A], A Action](s S) InitState[S, A]

type Message

type Message kit.Message

Alias types

type PushOpt

type PushOpt func(e *kit.Envelope)

func WithHdr

func WithHdr(key, value string) PushOpt

func WithHdrMap

func WithHdrMap(hdr map[string]string) PushOpt

type RESTParam

type RESTParam = fasthttp.Param

Alias types

type RESTParams

type RESTParams = fasthttp.Params

Alias types

type Selector

type Selector kit.RouteSelector

Alias types

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(opts ...ServerOption) *Server

func (*Server) PostmanCollection

func (s *Server) PostmanCollection(filename string) error

func (*Server) Run

func (s *Server) Run(ctx context.Context, signals ...os.Signal) error

func (*Server) Start

func (s *Server) Start(ctx context.Context) error

func (*Server) Stop

func (s *Server) Stop(ctx context.Context, signals ...os.Signal)

func (*Server) SwaggerAPI

func (s *Server) SwaggerAPI(filename string) error

type ServerOption

type ServerOption func(cfg *serverConfig)

func Listen

func Listen(addr string) ServerOption

func WithCORS

func WithCORS(cors CORSConfig) ServerOption

func WithCompression

func WithCompression(lvl CompressionLevel) ServerOption

func WithPredicateKey

func WithPredicateKey(key string) ServerOption

func WithServerName

func WithServerName(name string) ServerOption

func WithWebsocketEndpoint

func WithWebsocketEndpoint(endpoint string) ServerOption

type SetupContext

type SetupContext[S State[A], A Action] struct {
	// contains filtered or unexported fields
}

SetupContext is a context object which is holding information until the Server starts. It is used internally to hold state and server configuration.

func Setup

func Setup[S State[A], A Action](srv *Server, stateFactory InitState[S, A]) *SetupContext[S, A]

Setup is a helper function to set up server and services. Make sure S is a pointer to a struct, otherwise this function panics

type State

type State[A Action] interface {
	Name() string
	Reduce(action A)
}

State related types

type StreamCtx

type StreamCtx[S State[A], A Action, M Message] struct {
	// contains filtered or unexported fields
}

func (*StreamCtx) Conn

func (c *StreamCtx) Conn() kit.Conn

func (*StreamCtx) Context

func (c *StreamCtx) Context() context.Context

func (*StreamCtx) Next

func (c *StreamCtx) Next()

func (*StreamCtx[S, A, M]) Push

func (c *StreamCtx[S, A, M]) Push(m M, opt ...PushOpt) S

func (*StreamCtx[S, A, M]) PushTo

func (c *StreamCtx[S, A, M]) PushTo(conn kit.Conn, m M, opt ...PushOpt)

func (*StreamCtx) ReduceState

func (c *StreamCtx) ReduceState(action A, fn func(s S) error) (err error)

ReduceState is a helper function to reduce state and call fn if it's not nil. If you need to reduce the state in an atomic fashion, then you should pass a function fn which is guaranteed to be called in a locked state. Although, it only works if S implements sync.Locker interface.

func (*StreamCtx) Route

func (c *StreamCtx) Route() string

func (*StreamCtx) SetUserContext

func (c *StreamCtx) SetUserContext(userCtx context.Context)

func (*StreamCtx) State

func (c *StreamCtx) State() S

type StreamHandler

type StreamHandler[
	S State[A], A Action,
	IN, OUT Message,
] func(ctx *StreamCtx[S, A, OUT], in IN) error

type StreamOption

type StreamOption = stream.Option

Exposing internal types

func RPC

func RPC(predicate string, opt ...StreamSelectorOption) StreamOption

type StreamSelectorOption

type StreamSelectorOption = stream.SelectorOption

Exposing internal types

func StreamDecoder

func StreamDecoder(decoder DecoderFunc) StreamSelectorOption

type UnaryCtx

type UnaryCtx[S State[A], A Action] struct {
	// contains filtered or unexported fields
}

func (*UnaryCtx) Conn

func (c *UnaryCtx) Conn() kit.Conn

func (*UnaryCtx) Context

func (c *UnaryCtx) Context() context.Context

func (*UnaryCtx) Next

func (c *UnaryCtx) Next()

func (*UnaryCtx[S, A]) RESTConn

func (c *UnaryCtx[S, A]) RESTConn() (kit.RESTConn, bool)

func (*UnaryCtx) ReduceState

func (c *UnaryCtx) ReduceState(action A, fn func(s S) error) (err error)

ReduceState is a helper function to reduce state and call fn if it's not nil. If you need to reduce the state in an atomic fashion, then you should pass a function fn which is guaranteed to be called in a locked state. Although, it only works if S implements sync.Locker interface.

func (*UnaryCtx) Route

func (c *UnaryCtx) Route() string

func (*UnaryCtx) SetUserContext

func (c *UnaryCtx) SetUserContext(userCtx context.Context)

func (*UnaryCtx) State

func (c *UnaryCtx) State() S

type UnaryHandler

type UnaryHandler[
	S State[A], A Action,
	IN, OUT Message,
] func(ctx *UnaryCtx[S, A], in IN) (*OUT, error)

type UnaryOption

type UnaryOption = unary.Option

Exposing internal types

func DELETE

func DELETE(path string, opt ...UnarySelectorOption) UnaryOption

func GET

func GET(path string, opt ...UnarySelectorOption) UnaryOption
func HEAD(path string, opt ...UnarySelectorOption) UnaryOption

func OPTIONS

func OPTIONS(path string, opt ...UnarySelectorOption) UnaryOption

func PATCH

func PATCH(path string, opt ...UnarySelectorOption) UnaryOption

func POST

func POST(path string, opt ...UnarySelectorOption) UnaryOption

func PUT

func PUT(path string, opt ...UnarySelectorOption) UnaryOption

func REST

func REST(method, path string, opt ...UnarySelectorOption) UnaryOption

type UnarySelectorOption

type UnarySelectorOption = unary.SelectorOption

Exposing internal types

func UnaryDecoder

func UnaryDecoder(decoder DecoderFunc) UnarySelectorOption

func UnaryName

func UnaryName(name string) UnarySelectorOption

Directories

Path Synopsis
examples
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL