Implemented first pass of character and inventory services

This commit is contained in:
Brandon Watson
2023-08-14 19:22:40 -05:00
parent a9eed6f284
commit 6799b59f9c
9 changed files with 232 additions and 57 deletions

View File

@ -19,4 +19,9 @@ var Character = Type("character", func() {
Field(3, "description", String) Field(3, "description", String)
Field(4, "class", String) Field(4, "class", String)
Required("name", "description", "class") Required("name", "description", "class")
}) })
var InventoryRecord = Type("inventoryRecord", func() {
Field(1, "characterId", Int)
Field(2, "itemId", Int)
})

View File

@ -3,43 +3,82 @@ package characterapi
import ( import (
"context" "context"
character "crossnokaye-interview-assignment/services/character/gen/character" character "crossnokaye-interview-assignment/services/character/gen/character"
"errors"
"log" "log"
) )
// character service example implementation. // character service example implementation.
// The example methods log the requests and return zero values. // The example methods log the requests and return zero values.
type charactersrvc struct { type charactersrvc struct {
logger *log.Logger logger *log.Logger
characters map[int]*character.Character
} }
// NewCharacter returns the character service implementation. // NewCharacter returns the character service implementation.
func NewCharacter(logger *log.Logger) character.Service { func NewCharacter(logger *log.Logger) character.Service {
return &charactersrvc{logger} characterMap := make(map[int]*character.Character)
return &charactersrvc{logger, characterMap}
} }
// GetCharacter implements getCharacter. // GetCharacter implements getCharacter.
func (s *charactersrvc) GetCharacter(ctx context.Context, p int) (res *character.Character, err error) { func (s *charactersrvc) GetCharacter(ctx context.Context, p *character.GetCharacterPayload) (res *character.Character, err error) {
res = &character.Character{}
s.logger.Print("character.getCharacter") s.logger.Print("character.getCharacter")
itemToGet := s.characters[*p.ID]
if itemToGet == nil {
s.logger.Printf("character with id %d not found", &p.ID)
return nil, errors.New("character not found")
}
res = s.characters[*p.ID]
return return
} }
// CreateCharacter implements createCharacter. // CreateCharacter implements createCharacter.
func (s *charactersrvc) CreateCharacter(ctx context.Context, p *character.Character) (res *character.Character, err error) { func (s *charactersrvc) CreateCharacter(ctx context.Context, p *character.Character) (res *character.Character, err error) {
res = &character.Character{}
s.logger.Print("character.createCharacter") s.logger.Print("character.createCharacter")
id := -1
// Using this method of assigning IDs means they will
// potentially be non-sequential if ids are deleted
if p.ID != nil {
id = *p.ID
} else {
id = len(s.characters)
}
p.ID = &id
s.characters[id] = p
res = s.characters[id]
return return
} }
// UpdateCharacter implements updateCharacter. // UpdateCharacter implements updateCharacter.
func (s *charactersrvc) UpdateCharacter(ctx context.Context, p *character.Character) (res *character.Character, err error) { func (s *charactersrvc) UpdateCharacter(ctx context.Context, p *character.Character) (res *character.Character, err error) {
res = &character.Character{}
s.logger.Print("character.updateCharacter") s.logger.Print("character.updateCharacter")
itemToUpdate := s.characters[*p.ID]
if itemToUpdate == nil {
s.logger.Printf("characters with id %d not found", &p.ID)
return nil, errors.New("characters not found")
}
s.characters[*p.ID] = p
res = s.characters[*p.ID]
return return
} }
// DeleteCharacter implements deleteCharacter. // DeleteCharacter implements deleteCharacter.
func (s *charactersrvc) DeleteCharacter(ctx context.Context, p int) (err error) { func (s *charactersrvc) DeleteCharacter(ctx context.Context, p *character.DeleteCharacterPayload) (err error) {
s.logger.Print("character.deleteCharacter") s.logger.Print("character.deleteCharacter")
itemToDelete := s.characters[*p.ID]
if itemToDelete == nil {
s.logger.Printf("characters with id %d not found", &p.ID)
return errors.New("characters not found")
}
delete(s.characters, *p.ID)
return return
} }

View File

@ -9,17 +9,19 @@ import (
var _ = API("character", func() { var _ = API("character", func() {
Title("Character Srvice") Title("Character Srvice")
Server("character", func() { Server("character", func() {
Host("localhost", func() { Host("localhost", func() {
URI("grpc://localhost:8083") URI("grpc://localhost:8083")
}) })
}) })
}) })
var _ = Service("character", func() { var _ = Service("character", func() {
Description("A GRPC back service that handles CRUD operations for the characters and their attributes") Description("A GRPC back service that handles CRUD operations for the characters and their attributes")
Method("getCharacter", func() { Method("getCharacter", func() {
Payload(Int) Payload(func() {
Field(1, "id", Int)
})
Result(Character) Result(Character)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")
@ -61,7 +63,9 @@ var _ = Service("character", func() {
}) })
Method("deleteCharacter", func() { Method("deleteCharacter", func() {
Payload(Int) Payload(func() {
Field(1, "id", Int)
})
Result(Empty) Result(Empty)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")

View File

@ -21,14 +21,14 @@ func main() {
// Define command line flags, add any other flag required to configure the // Define command line flags, add any other flag required to configure the
// service. // service.
var ( var (
hostF = flag.String("host", "localhost", "Server host (valid values: localhost)") hostF = flag.String("host", "localhost", "Server host (valid values: localhost)")
domainF = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)") domainF = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)")
httpPortF = flag.String("http-port", "", "HTTP port (overrides host HTTP port specified in service design)") httpPortF = flag.String("http-port", "", "HTTP port (overrides host HTTP port specified in service design)")
secureF = flag.Bool("secure", false, "Use secure scheme (https or grpcs)") secureF = flag.Bool("secure", false, "Use secure scheme (https or grpcs)")
dbgF = flag.Bool("debug", false, "Log request and response bodies") dbgF = flag.Bool("debug", false, "Log request and response bodies")
itemAddr = flag.String("locator-addr", ":8082", "Item service address") itemAddr = flag.String("item-addr", ":8082", "Item service address")
//characterAddr = flag.String("locator-addr", ":8080", "Item service address") characterAddr = flag.String("character-addr", ":8083", "Character service address")
//inventoryAddr = flag.String("locator-addr", ":8081", "Item service address") inventoryAddr = flag.String("inventory-addr", ":8081", "Inventory service address")
) )
flag.Parse() flag.Parse()
@ -49,12 +49,29 @@ func main() {
} }
defer itemClientConnection.Close() defer itemClientConnection.Close()
inventoryClientConnection, err := grpc.Dial(*inventoryAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to connect to inventory service", err)
os.Exit(1)
}
defer inventoryClientConnection.Close()
characterClientConnection, err := grpc.Dial(*characterAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to connect to character service", err)
os.Exit(1)
}
defer characterClientConnection.Close()
// Initialize the services. // Initialize the services.
var ( var (
frontSvc front.Service frontSvc front.Service
) )
{ {
frontSvc = frontapi.NewFront(logger, itemClientConnection) frontSvc = frontapi.NewFront(logger, itemClientConnection, characterClientConnection,
inventoryClientConnection)
} }
// Wrap the services in endpoints that can be invoked from other services // Wrap the services in endpoints that can be invoked from other services

View File

@ -100,7 +100,6 @@ var _ = Service("front", func() {
HTTP(func() { HTTP(func() {
POST("/character") POST("/character")
Body(Character)
Response(StatusBadRequest) Response(StatusBadRequest)
Response(StatusOK) Response(StatusOK)
Response(StatusInternalServerError) Response(StatusInternalServerError)
@ -115,7 +114,6 @@ var _ = Service("front", func() {
HTTP(func() { HTTP(func() {
PUT("/character/{id}") PUT("/character/{id}")
Body(Character)
Response(StatusOK) Response(StatusOK)
Response(StatusBadRequest) Response(StatusBadRequest)
Response(StatusNotFound) Response(StatusNotFound)
@ -129,7 +127,7 @@ var _ = Service("front", func() {
Error("BadRequest") Error("BadRequest")
HTTP(func() { HTTP(func() {
POST("/character/{id}") DELETE("/character/{id}")
Response(StatusOK) Response(StatusOK)
Response(StatusBadRequest) Response(StatusBadRequest)
Response(StatusNotFound) Response(StatusNotFound)
@ -137,26 +135,26 @@ var _ = Service("front", func() {
}) })
Method("addItemToInventory", func() { Method("addItemToInventory", func() {
Payload(Int) Payload(InventoryRecord)
Result(Empty) Result(Empty)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")
HTTP(func() { HTTP(func() {
POST("/inventory/{CharacterId}") POST("/character/{characterId}/item")
Response(StatusOK) Response(StatusOK)
Response(StatusBadRequest) Response(StatusBadRequest)
}) })
}) })
Method("removeItemFromInventory", func() { Method("removeItemFromInventory", func() {
Payload(Int) Payload(InventoryRecord)
Result(Empty) Result(Empty)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")
HTTP(func() { HTTP(func() {
PUT("/inventory/{CharacterId}") DELETE("/character/{characterId}/item/{itemId}")
Response(StatusOK) Response(StatusOK)
Response(StatusBadRequest) Response(StatusBadRequest)
}) })

View File

@ -2,7 +2,11 @@ package frontapi
import ( import (
"context" "context"
genCharacter "crossnokaye-interview-assignment/services/character/gen/character"
genCharacterClient "crossnokaye-interview-assignment/services/character/gen/grpc/character/client"
front "crossnokaye-interview-assignment/services/front/gen/front" front "crossnokaye-interview-assignment/services/front/gen/front"
genInventoryClient "crossnokaye-interview-assignment/services/inventory/gen/grpc/inventory/client"
genInventory "crossnokaye-interview-assignment/services/inventory/gen/inventory"
genItemClient "crossnokaye-interview-assignment/services/item/gen/grpc/item/client" genItemClient "crossnokaye-interview-assignment/services/item/gen/grpc/item/client"
genItem "crossnokaye-interview-assignment/services/item/gen/item" genItem "crossnokaye-interview-assignment/services/item/gen/item"
goa "goa.design/goa/v3/pkg" goa "goa.design/goa/v3/pkg"
@ -17,24 +21,50 @@ type itemClient struct {
deleteItem goa.Endpoint deleteItem goa.Endpoint
} }
type characterClient struct {
getCharacter goa.Endpoint
createCharacter goa.Endpoint
updateCharacter goa.Endpoint
deleteCharacter goa.Endpoint
}
type inventoryClient struct {
addItem goa.Endpoint
removeItem goa.Endpoint
}
// front service example implementation. // front service example implementation.
// The example methods log the requests and return zero values. // The example methods log the requests and return zero values.
type frontsrvc struct { type frontsrvc struct {
logger *log.Logger logger *log.Logger
itemClient *itemClient itemClient *itemClient
characterClient *characterClient
inventoryClient *inventoryClient
} }
// NewFront returns the front service implementation. // NewFront returns the front service implementation.
func NewFront(logger *log.Logger, itemClientConnection *grpc.ClientConn) front.Service { func NewFront(logger *log.Logger, itemClientConnection *grpc.ClientConn, characterClientConnection *grpc.ClientConn,
inventoryClientConnection *grpc.ClientConn) front.Service {
ic := genItemClient.NewClient(itemClientConnection) ic := genItemClient.NewClient(itemClientConnection)
return &frontsrvc{logger: logger, itemClient: &itemClient{ cc := genCharacterClient.NewClient(characterClientConnection)
ic.GetItem(), icc := genInventoryClient.NewClient(inventoryClientConnection)
ic.CreateItem(), return &frontsrvc{logger: logger,
ic.UpdateItem(), itemClient: &itemClient{
ic.DeleteItem()}} ic.GetItem(),
ic.CreateItem(),
ic.UpdateItem(),
ic.DeleteItem()},
characterClient: &characterClient{
cc.GetCharacter(),
cc.CreateCharacter(),
cc.UpdateCharacter(),
cc.DeleteCharacter()},
inventoryClient: &inventoryClient{
icc.AddItem(),
icc.RemoveItem(),
}}
} }
// GetItem implements getItem. // GetItem implements getItem.
@ -86,40 +116,73 @@ func (s *frontsrvc) DeleteItem(ctx context.Context, p int) (err error) {
} }
// GetCharacter implements getCharacter. // GetCharacter implements getCharacter.
func (s *frontsrvc) GetCharacter(ctx context.Context, p int) (res *front.Character, err error) { func (s *frontsrvc) GetCharacter(ctx context.Context, id int) (res *front.Character, err error) {
res = &front.Character{}
s.logger.Print("front.getCharacter") s.logger.Print("front.getCharacter")
getCharacterResponse, err := s.characterClient.getCharacter(ctx, &genCharacter.GetCharacterPayload{ID: &id})
if err != nil {
return nil, err
}
character := getCharacterResponse.(*genCharacter.Character)
res = (*front.Character)(character)
return return
} }
// CreateCharacter implements createCharacter. // CreateCharacter implements createCharacter.
func (s *frontsrvc) CreateCharacter(ctx context.Context, p *front.Character) (res *front.Character, err error) { func (s *frontsrvc) CreateCharacter(ctx context.Context, p *front.Character) (res *front.Character, err error) {
res = &front.Character{}
s.logger.Print("front.createCharacter") s.logger.Print("front.createCharacter")
createCharacterResponse, err := s.characterClient.createCharacter(ctx, (*genCharacter.Character)(p))
if err != nil {
return nil, err
}
character := createCharacterResponse.(*genCharacter.Character)
res = (*front.Character)(character)
return return
} }
// UpdateCharacter implements updateCharacter. // UpdateCharacter implements updateCharacter.
func (s *frontsrvc) UpdateCharacter(ctx context.Context, p *front.Character) (res *front.Character, err error) { func (s *frontsrvc) UpdateCharacter(ctx context.Context, p *front.Character) (res *front.Character, err error) {
res = &front.Character{}
s.logger.Print("front.updateCharacter") s.logger.Print("front.updateCharacter")
updateCharacterResponse, err := s.characterClient.updateCharacter(ctx, (*genCharacter.Character)(p))
if err != nil {
return nil, err
}
character := updateCharacterResponse.(*genCharacter.Character)
res = (*front.Character)(character)
return return
} }
// DeleteCharacter implements deleteCharacter. // DeleteCharacter implements deleteCharacter.
func (s *frontsrvc) DeleteCharacter(ctx context.Context, p int) (err error) { func (s *frontsrvc) DeleteCharacter(ctx context.Context, p int) (err error) {
s.logger.Print("front.deleteCharacter") s.logger.Print("front.deleteCharacter")
_, err = s.characterClient.deleteCharacter(ctx, &genCharacter.DeleteCharacterPayload{ID: &p})
if err != nil {
return err
}
return return
} }
// AddItemToInventory implements addItemToInventory. // AddItemToInventory implements addItemToInventory.
func (s *frontsrvc) AddItemToInventory(ctx context.Context, p int) (err error) { func (s *frontsrvc) AddItemToInventory(ctx context.Context, p *front.InventoryRecord) (err error) {
s.logger.Print("front.addItemToInventory") s.logger.Print("front.addItemToInventory")
_, err = s.inventoryClient.addItem(ctx, (*genInventory.InventoryRecord)(p))
if err != nil {
return err
}
return return
} }
// RemoveItemFromInventory implements removeItemFromInventory. // RemoveItemFromInventory implements removeItemFromInventory.
func (s *frontsrvc) RemoveItemFromInventory(ctx context.Context, p int) (err error) { func (s *frontsrvc) RemoveItemFromInventory(ctx context.Context, p *front.InventoryRecord) (err error) {
s.logger.Print("front.removeItemFromInventory") s.logger.Print("front.removeItemFromInventory")
_, err = s.inventoryClient.removeItem(ctx, (*genInventory.InventoryRecord)(p))
if err != nil {
return err
}
return return
} }

View File

@ -1,16 +1,17 @@
package design package design
import ( import (
"crossnokaye-interview-assignment/design"
. "goa.design/goa/v3/dsl" . "goa.design/goa/v3/dsl"
) )
var _ = API("inventory", func() { var _ = API("inventory", func() {
Title("Inventory Service") Title("Inventory Service")
Server("inventory", func() { Server("inventory", func() {
Host("localhost", func() { Host("localhost", func() {
URI("grpc://localhost:8081") URI("grpc://localhost:8081")
}) })
}) })
}) })
var _ = Service("inventory", func() { var _ = Service("inventory", func() {
@ -19,7 +20,7 @@ var _ = Service("inventory", func() {
// }) // })
Method("addItem", func() { Method("addItem", func() {
Payload(Int) Payload(design.InventoryRecord)
Result(Empty) Result(Empty)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")
@ -30,7 +31,7 @@ var _ = Service("inventory", func() {
}) })
Method("removeItem", func() { Method("removeItem", func() {
Payload(Int) Payload(design.InventoryRecord)
Result(Empty) Result(Empty)
Error("NotFound") Error("NotFound")
Error("BadRequest") Error("BadRequest")

View File

@ -3,28 +3,76 @@ package inventoryapi
import ( import (
"context" "context"
inventory "crossnokaye-interview-assignment/services/inventory/gen/inventory" inventory "crossnokaye-interview-assignment/services/inventory/gen/inventory"
"errors"
"log" "log"
) )
// inventory service example implementation. // inventory service example implementation.
// The example methods log the requests and return zero values. // The example methods log the requests and return zero values.
type inventorysrvc struct { type inventorysrvc struct {
logger *log.Logger logger *log.Logger
inventories map[int]*[]int //key = characterId, value = array of itemIds
} }
// NewInventory returns the inventory service implementation. // NewInventory returns the inventory service implementation.
func NewInventory(logger *log.Logger) inventory.Service { func NewInventory(logger *log.Logger) inventory.Service {
return &inventorysrvc{logger} inventoryMap := make(map[int]*[]int)
return &inventorysrvc{logger, inventoryMap}
} }
// AddItem implements addItem. // AddItem implements addItem.
func (s *inventorysrvc) AddItem(ctx context.Context, p int) (err error) { func (s *inventorysrvc) AddItem(ctx context.Context, p *inventory.InventoryRecord) (err error) {
s.logger.Print("inventory.addItem") s.logger.Print("inventory.addItem")
itemList := s.inventories[*p.CharacterID]
if itemList == nil {
itemList = s.initCharacterInventory(p.CharacterID)
}
newItemList := append(*itemList, *p.ItemID)
s.inventories[*p.CharacterID] = &newItemList
return return
} }
// RemoveItem implements removeItem. // RemoveItem implements removeItem.
func (s *inventorysrvc) RemoveItem(ctx context.Context, p int) (err error) { func (s *inventorysrvc) RemoveItem(ctx context.Context, p *inventory.InventoryRecord) (err error) {
s.logger.Print("inventory.removeItem") s.logger.Print("inventory.removeItem")
itemList := s.inventories[*p.CharacterID]
if itemList == nil {
s.logger.Printf("inventory for character with id %d not found", p.CharacterID)
return errors.New("item not found")
}
idx := indexOf(*p.ItemID, *itemList)
if idx != -1 {
newItemList := remove(*itemList, idx)
s.inventories[*p.CharacterID] = &newItemList
} else {
s.logger.Printf("character with id %d does not have item %d in inventory", &p.CharacterID, &p.ItemID)
return errors.New("item not found for character")
}
return return
} }
func (s *inventorysrvc) initCharacterInventory(characterId *int) (itemList *[]int) {
list := make([]int, 0)
itemList = &list
s.inventories[*characterId] = &list
return
}
func indexOf(element int, data []int) int {
for k, v := range data {
if element == v {
return k
}
}
return -1 //not found.
}
func remove(s []int, i int) []int {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}

View File

@ -26,7 +26,7 @@ func (s *itemsrvc) GetItem(ctx context.Context, p *item.GetItemPayload) (res *it
itemToGet := s.items[*p.ID] itemToGet := s.items[*p.ID]
if itemToGet == nil { if itemToGet == nil {
s.logger.Printf("itemToGet with id %d not found", p.ID) s.logger.Printf("item with id %d not found", &p.ID)
return nil, errors.New("item not found") return nil, errors.New("item not found")
} }
res = s.items[*p.ID] res = s.items[*p.ID]
@ -59,7 +59,7 @@ func (s *itemsrvc) UpdateItem(ctx context.Context, p *item.Item) (res *item.Item
s.logger.Print("itemToGet.updateItem") s.logger.Print("itemToGet.updateItem")
itemToUpdate := s.items[*p.ID] itemToUpdate := s.items[*p.ID]
if itemToUpdate == nil { if itemToUpdate == nil {
s.logger.Printf("itemToGet with id %d not found", p.ID) s.logger.Printf("item with id %d not found", &p.ID)
return nil, errors.New("item not found") return nil, errors.New("item not found")
} }
s.items[*p.ID] = p s.items[*p.ID] = p
@ -73,7 +73,7 @@ func (s *itemsrvc) DeleteItem(ctx context.Context, p *item.DeleteItemPayload) (e
s.logger.Print("item.deleteItem") s.logger.Print("item.deleteItem")
itemToDelete := s.items[*p.ID] itemToDelete := s.items[*p.ID]
if itemToDelete == nil { if itemToDelete == nil {
s.logger.Printf("itemToGet with id %d not found", p.ID) s.logger.Printf("item with id %d not found", &p.ID)
return errors.New("item not found") return errors.New("item not found")
} }