refactor: new (simpler) version of the sl library

next
Antonio De Lucreziis 2 years ago
parent cacf58bc1d
commit be57412c40

@ -8,7 +8,6 @@ import (
"phc/website/services/database"
"phc/website/services/server"
"phc/website/services/server/dev"
lista_utenti "phc/website/services/server/lista-utenti"
"phc/website/sl"
)
@ -17,23 +16,20 @@ func main() {
// sl.Inject[config.Interface](l, &config.EnvConfig{})
sl.InjectValue[config.Interface](l, &config.Custom{
ModeValue: "production",
HostValue: ":4000",
sl.InjectValue(l, config.Slot, &config.Config{
Mode: "production",
Host: ":4000",
})
sl.InjectValue[database.Database](l, &database.Memory{})
sl.InjectValue(l, database.Slot, database.Database(
&database.Memory{}),
)
sl.Inject(l, &dev.Dev{})
sl.Inject(l, &lista_utenti.ListaUtenti{})
sl.Inject(l, &server.Server{})
_, err := sl.Use[*server.Server](l)
if err != nil {
if _, err := server.Configure(l); err != nil {
log.Fatal(err)
}
dev, err := sl.Use[*dev.Dev](l)
dev, err := sl.Use(l, dev.Slot)
if err != nil {
log.Fatal(err)
}
@ -48,4 +44,6 @@ func main() {
if err := enc.Encode(dev.HtmlRouteBindings); err != nil {
log.Fatal(err)
}
log.Printf(`generated "out/routes.json"`)
}

@ -6,22 +6,18 @@ import (
"phc/website/services/config"
"phc/website/services/database"
"phc/website/services/server"
"phc/website/services/server/dev"
lista_utenti "phc/website/services/server/lista-utenti"
"phc/website/sl"
)
func main() {
l := sl.New()
// sl.Inject[config.Interface](l, &config.EnvConfig{})
config := sl.InjectValue[config.Interface](l, &config.Custom{
ModeValue: "production",
HostValue: ":4000",
cfg := sl.InjectValue(l, config.Slot, &config.Config{
Mode: "production",
Host: ":4000",
})
sl.InjectValue[database.Database](l, &database.Memory{
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{
{
Id: "claire",
@ -38,14 +34,10 @@ func main() {
},
})
sl.Inject(l, &dev.Dev{})
sl.Inject(l, &lista_utenti.ListaUtenti{})
sl.Inject(l, &server.Server{})
server, err := sl.Use[*server.Server](l)
srv, err := server.Configure(l)
if err != nil {
log.Fatal(err)
}
log.Fatal(server.Router.Listen(config.Host()))
log.Fatal(srv.Router.Listen(cfg.Host))
}

@ -6,47 +6,30 @@ import (
"github.com/joho/godotenv"
)
type Interface interface {
sl.Service
var Slot = sl.NewSlot[*Config]()
Mode() string
Host() string
type Config struct {
Mode string
Host string
}
type Custom struct {
ModeValue string
HostValue string
}
func (c Custom) Mode() string { return c.ModeValue }
func (c Custom) Host() string { return c.HostValue }
func (Custom) Initialize(l *sl.ServiceLocator) error {
return nil
}
type EnvConfig struct {
mode string
host string
}
func (c EnvConfig) Mode() string { return c.mode }
func (c EnvConfig) Host() string { return c.host }
func (c *EnvConfig) Initialize(l *sl.ServiceLocator) error {
func Load(l *sl.ServiceLocator) (*Config, error) {
m, err := godotenv.Read(".env")
if err != nil {
return err
return nil, err
}
c.mode = "production"
mode := "production"
if v, ok := m["MODE"]; ok {
c.mode = v
mode = v
}
c.host = ":4000"
host := ":4000"
if v, ok := m["HOST"]; ok {
c.host = v
host = v
}
return nil
return &Config{
mode,
host,
}, nil
}

@ -6,9 +6,9 @@ import (
"phc/website/sl"
)
type Database interface {
sl.Service
var Slot = sl.NewSlot[Database]()
type Database interface {
CreateUser(user model.User) error
ReadUser(id string) (model.User, error)
ReadUsers() ([]model.User, error)
@ -20,8 +20,6 @@ type Memory struct {
Users []model.User
}
func (m *Memory) Initialize(l *sl.ServiceLocator) error { return nil }
func (m *Memory) CreateUser(user model.User) error {
m.Users = append(m.Users, user)
return nil

@ -9,35 +9,39 @@ import (
"github.com/gofiber/fiber/v2"
)
var Slot = sl.NewSlot[*Dev]()
type Dev struct {
HtmlRouteBindings map[string]string
}
func (m *Dev) Initialize(l *sl.ServiceLocator) error {
m.HtmlRouteBindings = map[string]string{}
func New(l *sl.ServiceLocator) (*Dev, error) {
dev := &Dev{
map[string]string{},
}
router, err := sl.Use[*routes.Router](l)
router, err := sl.Use(l, routes.Api)
if err != nil {
return err
return nil, err
}
router.Get("/api/dev/routes", func(c *fiber.Ctx) error {
return c.JSON(m.HtmlRouteBindings)
router.Get("/dev/routes", func(c *fiber.Ctx) error {
return c.JSON(dev.HtmlRouteBindings)
})
return nil
return dev, nil
}
// UseVitePage this hook will link the provided "mountPoint" to the "frontendHtml" page
func UseVitePage[T any](l *sl.ServiceLocator, mountPoint, frontendHtml string) func(c *fiber.Ctx) error {
func UseVitePage(l *sl.ServiceLocator, mountPoint, frontendHtml string) func(c *fiber.Ctx) error {
log.Printf(`registering vite route %q for %q`, frontendHtml, mountPoint)
dev, err := sl.Use[*Dev](l)
dev, err := sl.Use(l, Slot)
if err != nil {
log.Fatal(err)
}
dev.HtmlRouteBindings[mountPoint] = path.Join("./frontend/", frontendHtml)
dev.HtmlRouteBindings[mountPoint] = frontendHtml
return func(c *fiber.Ctx) error {
return c.SendFile(path.Join("./out/frontend/", frontendHtml))

@ -9,24 +9,22 @@ import (
"github.com/gofiber/fiber/v2"
)
type ListaUtenti struct{}
func (s *ListaUtenti) Initialize(l *sl.ServiceLocator) error {
db, err := sl.Use[database.Database](l)
func Configure(l *sl.ServiceLocator) error {
db, err := sl.Use(l, database.Slot)
if err != nil {
return err
}
router, err := sl.Use[*routes.Router](l)
r, err := sl.Use(l, routes.Root)
if err != nil {
return err
}
router.Get("/utenti",
dev.UseVitePage[ListaUtenti](l, "/utenti", "pages/lista-utenti/index.html"),
r.Get("/utenti",
dev.UseVitePage(l, "/utenti", "pages/lista-utenti/index.html"),
)
router.Get("/api/lista-utenti", func(c *fiber.Ctx) error {
r.Get("/api/lista-utenti", func(c *fiber.Ctx) error {
users, err := db.ReadUsers()
if err != nil {
return err

@ -8,8 +8,6 @@ import (
"net/http"
"phc/website/model"
"phc/website/services/database"
"phc/website/services/server/dev"
lista_utenti "phc/website/services/server/lista-utenti"
"phc/website/services/server/routes"
"phc/website/sl"
"testing"
@ -22,7 +20,7 @@ import (
func Test1(t *testing.T) {
l := sl.New()
sl.InjectValue[database.Database](l, &database.Memory{
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{
{
Id: "claire",
@ -39,11 +37,8 @@ func Test1(t *testing.T) {
},
})
sl.Inject(l, &dev.Dev{})
sl.Inject(l, &lista_utenti.ListaUtenti{})
r := fiber.New()
sl.InjectValue(l, routes.NewRouter(r))
sl.InjectValue(l, routes.Root, fiber.Router(r))
req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil)
if err != nil {

@ -6,10 +6,6 @@ import (
"github.com/gofiber/fiber/v2"
)
func NewRouter(r fiber.Router) *Router {
return &Router{r}
}
var Root = sl.NewSlot[fiber.Router]()
type Router struct{ fiber.Router }
func (s *Router) Initialize(l *sl.ServiceLocator) error { return nil }
var Api = sl.NewSlot[fiber.Router]()

@ -11,16 +11,21 @@ import (
type Server struct{ Router *fiber.App }
func (s *Server) Initialize(l *sl.ServiceLocator) error {
s.Router = fiber.New(fiber.Config{})
sl.InjectValue(l, routes.NewRouter(s.Router))
func Configure(l *sl.ServiceLocator) (*Server, error) {
r := fiber.New(fiber.Config{})
if _, err := sl.Use[*dev.Dev](l); err != nil {
return err
sl.InjectValue(l, routes.Root, r.Group("/"))
sl.InjectValue(l, routes.Api, r.Group("/api"))
devServerInterop, err := dev.New(l)
if err != nil {
return nil, err
}
if _, err := sl.Use[*lista_utenti.ListaUtenti](l); err != nil {
return err
sl.InjectValue(l, dev.Slot, devServerInterop)
if err := lista_utenti.Configure(l); err != nil {
return nil, err
}
return nil
return &Server{r}, nil
}

@ -5,69 +5,146 @@ import (
"log"
)
type provider struct {
initialized bool
service Service
}
type SlotKey[T any] *struct{}
type ServiceLocator struct {
providers map[string]*provider
hooks map[string][]Service
func NewSlot[T any]() SlotKey[T] {
return SlotKey[T](new(struct{}))
}
type Service interface {
Initialize(l *ServiceLocator) error
type slot struct {
createFunc func(*ServiceLocator) (any, error)
created bool
value any
typ string
}
func New() *ServiceLocator {
return &ServiceLocator{
providers: map[string]*provider{},
hooks: map[string][]Service{},
func (s *slot) checkInitialized(l *ServiceLocator) error {
if !s.created {
v, err := s.createFunc(l)
if err != nil {
return err
}
log.Printf(`initialized lazy value of type %T for slot of type %s`, v, s.typ)
s.value = v
}
}
func getTypeName[T any]() string {
var v T
return fmt.Sprintf(`%T`, &v)[1:]
return nil
}
// Inject will set the implementation for "S" to "value" (the service will be initialized when needed after all of its dependencies)
func Inject[S Service](l *ServiceLocator, value S) {
key := getTypeName[S]()
log.Printf(`injecting value of type %T for interface %s`, value, key)
type ServiceLocator struct {
providers map[any]*slot
}
l.providers[key] = &provider{false, value}
func New() *ServiceLocator {
return &ServiceLocator{
providers: map[any]*slot{},
}
}
// InjectValue will set the implementation for "S" to "value" and mark this service as already initialized (as this is just a constant)
func InjectValue[S Service](l *ServiceLocator, value S) S {
key := getTypeName[S]()
log.Printf(`injecting value of type %T for interface %s`, value, key)
func InjectValue[T any](l *ServiceLocator, slotKey SlotKey[T], value T) T {
log.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]())
l.providers[key] = &provider{true, value}
l.providers[slotKey] = &slot{nil, true, value, getTypeName[T]()}
return value
}
// Use will retrive from the service locator the implementation set for the type "T" and initialize the service if yet to be initialized
func Use[T Service](l *ServiceLocator) (T, error) {
provider, ok := l.providers[getTypeName[T]()]
if !ok {
var zero T
return zero, fmt.Errorf(`no injected value for type "%T"`, zero)
func InjectLazy[T any](l *ServiceLocator, slotKey SlotKey[T], createFunc func(*ServiceLocator) (T, error)) {
log.Printf(`injected lazy for slot of type %s`, getTypeName[T]())
l.providers[slotKey] = &slot{
createFunc: func(l *ServiceLocator) (any, error) {
return createFunc(l)
},
created: false,
value: nil,
typ: getTypeName[T](),
}
}
func Use[T any](l *ServiceLocator, slotKey SlotKey[T]) (T, error) {
var zero T
if provider.initialized {
service := provider.service.(T)
return service, nil
slot, ok := l.providers[slotKey]
if !ok {
return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]())
}
log.Printf(`initializing %T`, provider.service)
if err := provider.service.Initialize(l); err != nil {
var zero T
err := slot.checkInitialized(l)
if err != nil {
return zero, err
}
provider.initialized = true
service := provider.service.(T)
return service, nil
v := slot.value.(T)
log.Printf(`using slot of type %s with value of type %T`, getTypeName[T](), v)
return v, nil
}
// // Require forces the initialization of a slot
// func Require[T any](l *ServiceLocator, slotKey SlotKey[T]) error {
// var zero T
// slot, ok := l.providers[slotKey]
// if !ok {
// return fmt.Errorf(`no injected value for type %T`, zero)
// }
// return slot.checkInitialized(l)
// }
// // MustRequire forces the initialization of a slot or panics if the slot is missing
// func MustRequire[T any](l *ServiceLocator, slotKey SlotKey[T]) {
// err := Require(l, slotKey)
// if err != nil {
// panic(err)
// }
// }
// // Inject will set the implementation for "S" to "value" (the service will be initialized when needed after all of its dependencies)
// func Inject[S Service](l *Context, value S) {
// key := getTypeName[S]()
// log.Printf(`injecting value of type %T for interface %s`, value, key)
// l.providers[key] = &provider{false, nil, value}
// }
// // InjectValue will set the implementation for "S" to "value" and mark this service as already initialized (as this is just a constant)
// func InjectValue[S Service](l *Context, value S) S {
// key := getTypeName[S]()
// log.Printf(`injecting value of type %T for interface %s`, value, key)
// l.providers[key] = &provider{true, nil, value}
// return value
// }
// // Use will retrive from the service locator the implementation set for the type "T" and initialize the service if yet to be initialized
// func Use[T Service](l *Context) (T, error) {
// provider, ok := l.providers[getTypeName[T]()]
// if !ok {
// var zero T
// return zero, fmt.Errorf(`no injected value for type "%T"`, zero)
// }
// if provider.initialized {
// service := provider.service.(T)
// return service, nil
// }
// log.Printf(`initializing %T`, provider.service)
// if err := provider.service.Initialize(l); err != nil {
// var zero T
// return zero, err
// }
// provider.initialized = true
// service := provider.service.(T)
// return service, nil
// }
func getTypeName[T any]() string {
var zero T
return fmt.Sprintf(`%T`, &zero)[1:]
}

@ -17,7 +17,6 @@ const retriveGoRoutes = {
const routesReq = await fetch('http://127.0.0.1:4000/api/dev/routes')
const routes = await routesReq.json()
console.dir(routes, { depth: null })
return routes
},
@ -51,8 +50,8 @@ export default defineConfig(async config => {
configureServer(server) {
Object.entries(routes).forEach(([route, file]) => {
server.middlewares.use(route, async (req, res, next) => {
let htmlPage = await readFile(resolve(__dirname, file), 'utf8')
htmlPage = htmlPage.replace(/\.\//g, dirname(file) + '/')
let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
const url = file
const html = await server.transformIndexHtml(url, htmlPage)

Loading…
Cancel
Save