본문 바로가기

GO lang/web

[GO] Todo list - interface 구현2

sqllite 로 변경하는 중간과정으로 interface 구조를 구현하던중, DB 의 Close 기능은 main.go 까지 전달해야해서 변경사항이 예상보다 많았습니다.

1. 기존에 model 에서 처리하던 interface 이름을 공개하여 app.go -> main.go 까지 전달함.

2. 해당 interface 이름을 공개하며 인터페이스 및 매소드명을 대문자로 모두 변경함.

 

기본 구조는 아래와 같음.

 

 

model.go

 

package model

import (
	"time"
)

type Todo struct {
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	Completed bool      `json:"completed"`
	CreatedAt time.Time `json:"created_at"`
}

// 인터페이스 외부로 공개를 해야함, close 함수의 권한을 main.go 에 넘겨주기위해서
// 인터페이스 이름 및 내부 함수들을 대문자로변경, 외부에 공개
type DBHandler interface {
	GetTodos() []*Todo
	AddTodo(name string) *Todo
	RemoveTodo(id int) bool
	CompleteTodo(id int, complete bool) bool
	Close()
}

// init -> NewDBHandler
func NewDBHandler() DBHandler {
	// handler = newMemoryHandler()
	// sqlite hanlder 만들면 아래처럼 변경만 하면 됨.
	// handler = newSqliteHandler()
	return newMemoryHandler()
}

 

memoryHandler.go - 매소드 이름을 모두 대문자로 변경 

 

package model

import (
	"log"
	"time"
)

type memoryHandler struct {
	todoMap map[int]*Todo
}

func (m *memoryHandler) GetTodos() []*Todo {
	list := []*Todo{}
	for _, v := range m.todoMap {
		list = append(list, v)
	}
	return list
}

func (m *memoryHandler) AddTodo(name string) *Todo {
	id := len(m.todoMap) + 1
	todo := &Todo{id, name, false, time.Now()}
	m.todoMap[id] = todo
	log.Println("add Todo success")
	return todo
}

func (m *memoryHandler) RemoveTodo(id int) bool {
	if _, ok := m.todoMap[id]; ok {
		delete(m.todoMap, id)
		return true
	}
	return false
}

func (m *memoryHandler) CompleteTodo(id int, complete bool) bool {
	if todo, ok := m.todoMap[id]; ok {
		todo.Completed = complete
		return true
	}
	return false
}

func (m *memoryHandler) Close() {

}

func newMemoryHandler() DBHandler {
	m := &memoryHandler{}
	m.todoMap = make(map[int]*Todo)
	return m
}

 

sqliteHandler.go - 신규추가, 기능 구현 미완료.

 

package model

import (
	"database/sql"

	_ "github.com/mattn/go-sqlite3"
)

type sqliteHandler struct {
	db *sql.DB
}

func (s *sqliteHandler) GetTodos() []*Todo {
	return nil
}

func (s *sqliteHandler) AddTodo(name string) *Todo {
	return nil
}

func (s *sqliteHandler) RemoveTodo(id int) bool {
	return false
}

func (s *sqliteHandler) CompleteTodo(id int, complete bool) bool {
	return false
}

func (s *sqliteHandler) Close() {
	s.db.Close()
}

func newSqliteHandler() DBHandler {
	database, err := sql.Open("sqlite3", "./test.db")
	if err != nil {
		panic(err)
	}
	statement, _ := database.Prepare(
		`CREATE TABLE IF NOT EXISTS todos (
			id        INTEGER  PRIMARY KEY AUTOINCREMENT,
			name      TEXT,
			completed BOOLEAN,
			createdAt DATETIME
		)`)
	statement.Exec()
	return &sqliteHandler{}
}

 

app.go - sqlite handler 를 포함한 구조체를 전체를 전달

 

package myapp

import (
	"GO/tuckersGo/goWeb/web18-todo_sqlite/model"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/unrolled/render"
)

var rd *render.Render = render.New()

// 인터페이스 외부로 공개를 해야함, close 함수의 권한을 main.go 에 넘겨주기위해서
// app.go 에도 언제 close 할지 모르므로 main.go 로 이관.
// 그래서 AppHandler 만들어서 넘겨줌.
type AppHandler struct {
	// 임베디드 처리함. 이유는 ??? 상속과 비슷한데 조금 다름 is 관계는 아니고, has 관계임.
	// 이름을 암시적으로 생략함. handler 생략함.
	http.Handler
	db model.DBHandler
}

func (a *AppHandler) indexHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/todo.html", http.StatusTemporaryRedirect)
}

func (a *AppHandler) getTodoListHandler(w http.ResponseWriter, r *http.Request) {

	list := a.db.GetTodos()
	rd.JSON(w, http.StatusOK, list)
}

func (a *AppHandler) addTodoHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	todo := a.db.AddTodo(name)
	rd.JSON(w, http.StatusCreated, todo)
}

type Success struct {
	Success bool `json:"success"`
}

func (a *AppHandler) removeTodoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	ok := a.db.RemoveTodo(id)
	if ok {
		rd.JSON(w, http.StatusOK, Success{Success: true})
	} else {
		rd.JSON(w, http.StatusOK, Success{Success: false})
	}
}

func (a *AppHandler) completeTodoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	complete := r.FormValue("complete") == "true"
	ok := a.db.CompleteTodo(id, complete)
	if ok {
		rd.JSON(w, http.StatusOK, Success{Success: true})
	} else {
		rd.JSON(w, http.StatusOK, Success{Success: false})
	}
}

func (a *AppHandler) Close() {
	a.db.Close()
}

// 리턴변경 http.Handler -> AppHandler
func MakeNewHandler() *AppHandler {

	mux := mux.NewRouter()
	a := &AppHandler{
		Handler: mux,
		db:      model.NewDBHandler(),
	}
	mux.HandleFunc("/", a.indexHandler)
	mux.HandleFunc("/todos", a.getTodoListHandler).Methods("GET")
	mux.HandleFunc("/todos", a.addTodoHandler).Methods("POST")
	mux.HandleFunc("/todos/{id:[0-9]+}", a.removeTodoHandler).Methods("DELETE")
	mux.HandleFunc("/complete-todo/{id:[0-9]+}", a.completeTodoHandler).Methods("GET")
	return a
}

 

main.go - DB Close 호출. 이부분을 위해서 하위 구조를 변경함

 

package main

import (
	"GO/tuckersGo/goWeb/web18-todo_sqlite/myapp"
	"log"
	"net/http"

	"github.com/urfave/negroni"
)

const portNumber = ":3000"

func main() {
	mux := myapp.MakeNewHandler()
	defer mux.Close()

	ng := negroni.Classic()
	ng.UseHandler(mux)

	log.Println("Started App")
	err := http.ListenAndServe(portNumber, ng)
	if err != nil {
		panic(err)
	}

}

 

테스트 결과 정상

 

PS D:\workspace\GO\tuckersGo\goWeb\web19-todo_sqlite\myapp> go test
2021/11/18 00:12:36 add Todo success
2021/11/18 00:12:36 app_test.go / add result > {1 Test todo false 2021-11-18 00:12:36.0639536 +0100 CET} 1
2021/11/18 00:12:36 add Todo success
2021/11/18 00:12:36 app_test.go / add result > {2 Test todo2 false 2021-11-18 00:12:36.0986662 +0100 CET} 2
2021/11/18 00:12:36 after getting whole data > {2 Test todo2 false 2021-11-18 00:12:36.0986662 +0100 CET}
2021/11/18 00:12:36 after getting whole data > {1 Test todo false 2021-11-18 00:12:36.0639536 +0100 CET}
2021/11/18 00:12:36 after complete for > {1 Test todo false 2021-11-18 00:12:36.0639536 +0100 CET}
2021/11/18 00:12:36 after complete for > {2 Test todo2 true 2021-11-18 00:12:36.0986662 +0100 CET}
2021/11/18 00:12:36 delete id1:  1
2021/11/18 00:12:36 after delete for > {2 Test todo2 true 2021-11-18 00:12:36.0986662 +0100 CET}
PASS
ok      GO/tuckersGo/goWeb/web18-todo_sqlite/myapp      0.375s
PS D:\workspace\GO\tuckersGo\goWeb\web19-todo_sqlite\myapp>

 

그리고 문법적인 오류가 발생하지 않는다면 ./myapp 폴더에 test.db 파일도 생성된것을 볼 수 있다.

'GO lang > web' 카테고리의 다른 글

[GO] Todo list - Google Oauth2  (0) 2021.11.23
[GO] Todo list - sqlite  (0) 2021.11.18
[GO] Todo list - interface 구현1  (0) 2021.11.18
[GO] Todo list - refactoring(구조변경)  (0) 2021.11.17
[GO] Todo list - map 자료구조  (0) 2021.11.17