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

@ -6,22 +6,18 @@ import (
"phc/website/services/config" "phc/website/services/config"
"phc/website/services/database" "phc/website/services/database"
"phc/website/services/server" "phc/website/services/server"
"phc/website/services/server/dev"
lista_utenti "phc/website/services/server/lista-utenti"
"phc/website/sl" "phc/website/sl"
) )
func main() { func main() {
l := sl.New() l := sl.New()
// sl.Inject[config.Interface](l, &config.EnvConfig{}) cfg := sl.InjectValue(l, config.Slot, &config.Config{
Mode: "production",
config := sl.InjectValue[config.Interface](l, &config.Custom{ Host: ":4000",
ModeValue: "production",
HostValue: ":4000",
}) })
sl.InjectValue[database.Database](l, &database.Memory{ sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{ Users: []model.User{
{ {
Id: "claire", Id: "claire",
@ -38,14 +34,10 @@ func main() {
}, },
}) })
sl.Inject(l, &dev.Dev{}) srv, err := server.Configure(l)
sl.Inject(l, &lista_utenti.ListaUtenti{})
sl.Inject(l, &server.Server{})
server, err := sl.Use[*server.Server](l)
if err != nil { if err != nil {
log.Fatal(err) 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" "github.com/joho/godotenv"
) )
type Interface interface { var Slot = sl.NewSlot[*Config]()
sl.Service
Mode() string type Config struct {
Host() string Mode string
Host string
} }
type Custom struct { func Load(l *sl.ServiceLocator) (*Config, error) {
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 {
m, err := godotenv.Read(".env") m, err := godotenv.Read(".env")
if err != nil { if err != nil {
return err return nil, err
} }
c.mode = "production" mode := "production"
if v, ok := m["MODE"]; ok { if v, ok := m["MODE"]; ok {
c.mode = v mode = v
} }
c.host = ":4000" host := ":4000"
if v, ok := m["HOST"]; ok { 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" "phc/website/sl"
) )
type Database interface { var Slot = sl.NewSlot[Database]()
sl.Service
type Database interface {
CreateUser(user model.User) error CreateUser(user model.User) error
ReadUser(id string) (model.User, error) ReadUser(id string) (model.User, error)
ReadUsers() ([]model.User, error) ReadUsers() ([]model.User, error)
@ -20,8 +20,6 @@ type Memory struct {
Users []model.User Users []model.User
} }
func (m *Memory) Initialize(l *sl.ServiceLocator) error { return nil }
func (m *Memory) CreateUser(user model.User) error { func (m *Memory) CreateUser(user model.User) error {
m.Users = append(m.Users, user) m.Users = append(m.Users, user)
return nil return nil

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

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

@ -8,8 +8,6 @@ import (
"net/http" "net/http"
"phc/website/model" "phc/website/model"
"phc/website/services/database" "phc/website/services/database"
"phc/website/services/server/dev"
lista_utenti "phc/website/services/server/lista-utenti"
"phc/website/services/server/routes" "phc/website/services/server/routes"
"phc/website/sl" "phc/website/sl"
"testing" "testing"
@ -22,7 +20,7 @@ import (
func Test1(t *testing.T) { func Test1(t *testing.T) {
l := sl.New() l := sl.New()
sl.InjectValue[database.Database](l, &database.Memory{ sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{ Users: []model.User{
{ {
Id: "claire", 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() 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) req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil)
if err != nil { if err != nil {

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

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

@ -5,69 +5,146 @@ import (
"log" "log"
) )
type provider struct { type SlotKey[T any] *struct{}
initialized bool
service Service
}
type ServiceLocator struct { func NewSlot[T any]() SlotKey[T] {
providers map[string]*provider return SlotKey[T](new(struct{}))
hooks map[string][]Service
} }
type Service interface { type slot struct {
Initialize(l *ServiceLocator) error createFunc func(*ServiceLocator) (any, error)
created bool
value any
typ string
} }
func New() *ServiceLocator { func (s *slot) checkInitialized(l *ServiceLocator) error {
return &ServiceLocator{ if !s.created {
providers: map[string]*provider{}, v, err := s.createFunc(l)
hooks: map[string][]Service{}, 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 { return nil
var v T
return fmt.Sprintf(`%T`, &v)[1:]
} }
// Inject will set the implementation for "S" to "value" (the service will be initialized when needed after all of its dependencies) type ServiceLocator struct {
func Inject[S Service](l *ServiceLocator, value S) { providers map[any]*slot
key := getTypeName[S]() }
log.Printf(`injecting value of type %T for interface %s`, value, key)
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[T any](l *ServiceLocator, slotKey SlotKey[T], value T) T {
func InjectValue[S Service](l *ServiceLocator, value S) S { log.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]())
key := getTypeName[S]()
log.Printf(`injecting value of type %T for interface %s`, value, key)
l.providers[key] = &provider{true, value} l.providers[slotKey] = &slot{nil, true, value, getTypeName[T]()}
return 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 InjectLazy[T any](l *ServiceLocator, slotKey SlotKey[T], createFunc func(*ServiceLocator) (T, error)) {
func Use[T Service](l *ServiceLocator) (T, error) { log.Printf(`injected lazy for slot of type %s`, getTypeName[T]())
provider, ok := l.providers[getTypeName[T]()]
if !ok { l.providers[slotKey] = &slot{
var zero T createFunc: func(l *ServiceLocator) (any, error) {
return zero, fmt.Errorf(`no injected value for type "%T"`, zero) 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 { slot, ok := l.providers[slotKey]
service := provider.service.(T) if !ok {
return service, nil return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]())
} }
log.Printf(`initializing %T`, provider.service) err := slot.checkInitialized(l)
if err := provider.service.Initialize(l); err != nil { if err != nil {
var zero T
return zero, err return zero, err
} }
provider.initialized = true v := slot.value.(T)
service := provider.service.(T)
return service, nil 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 routesReq = await fetch('http://127.0.0.1:4000/api/dev/routes')
const routes = await routesReq.json() const routes = await routesReq.json()
console.dir(routes, { depth: null })
return routes return routes
}, },
@ -51,8 +50,8 @@ export default defineConfig(async config => {
configureServer(server) { configureServer(server) {
Object.entries(routes).forEach(([route, file]) => { Object.entries(routes).forEach(([route, file]) => {
server.middlewares.use(route, async (req, res, next) => { server.middlewares.use(route, async (req, res, next) => {
let htmlPage = await readFile(resolve(__dirname, file), 'utf8') let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
htmlPage = htmlPage.replace(/\.\//g, dirname(file) + '/') htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
const url = file const url = file
const html = await server.transformIndexHtml(url, htmlPage) const html = await server.transformIndexHtml(url, htmlPage)

Loading…
Cancel
Save