Back to Blog
Go Language

Golang Todolist CLI #2 – Membuat Model dan Repository Task

8 Mei 20253 min read
Golang Todolist CLI #2 – Membuat Model dan Repository Task

Selamat datang kembali di seri Golang Todolist CLI bersama saya, Ajitama! 🎉

Pada bagian sebelumnya, kita telah berhasil menginisialisasi proyek. Kali ini, kita akan mulai membuat struktur data dan lapisan penyimpanan data: model Task dan repository-nya.


📁 Struktur Folder

Sebelum kita mulai, mari kita perbaiki struktur proyek agar lebih idiomatik sesuai best practice Golang:

plaintext
golang-todolist-cli/
├── go.mod
├── internal/
│   ├── model/
│   │   └── task.go
│   └── repository/
│       ├── task_repository.go
│       └── task_repository_test.go

Kita akan menyimpan kode domain aplikasi (seperti model dan repository) di dalam folder internal/, karena ini merupakan praktik umum untuk membatasi akses antar package.


1️⃣ Membuat Model Task

Pertama, kita buat struktur data Task di file internal/model/task.go:

go
package model

import "time"

type Task struct {
	Id          int
	Description string
	CreatedAt   time.Time
	CompletedAt *time.Time
}

Struktur Task ini menyimpan informasi deskripsi tugas, waktu dibuat, dan waktu selesai (jika sudah diselesaikan).


2️⃣ Membuat Task Repository

Selanjutnya, kita buat repository untuk menyimpan dan mengelola data Task. Di sini, kita gunakan penyimpanan in-memory agar mudah dan ringan untuk tahap awal.

File: internal/repository/task_repository.go

go
package repository

import (
	"fmt"
	"time"

	"github.com/fardannozami/golang-todolist-cli/internal/model"
)

type TaskRepository interface {
	AddTask(task model.Task) error
	GetAllTasks() ([]model.Task, error)
	DeleteTaskById(id int) error
	MarkTaskAsCompleted(id int) error
}

type InMemoryTaskRepository struct {
	tasks []model.Task
}

func NewInMemoryTaskRepository() *InMemoryTaskRepository {
	return &InMemoryTaskRepository{
		tasks: make([]model.Task, 0),
	}
}

func (r *InMemoryTaskRepository) AddTask(task model.Task) error {
	r.tasks = append(r.tasks, task)
	return nil
}

func (r *InMemoryTaskRepository) GetAllTasks() ([]model.Task, error) {
	taskCopy := make([]model.Task, len(r.tasks))
	copy(taskCopy, r.tasks)
	return taskCopy, nil
}

func (r *InMemoryTaskRepository) DeleteTaskById(id int) error {
	for i, task := range r.tasks {
		if task.Id == id {
			r.tasks = append(r.tasks[:i], r.tasks[i+1:]...)
			return nil
		}
	}
	return fmt.Errorf("task with id %d not found", id)
}

func (r *InMemoryTaskRepository) MarkTaskAsCompleted(id int) error {
	for i, task := range r.tasks {
		if task.Id == id {
			completedAt := time.Now()
			r.tasks[i].CompletedAt = &completedAt
			return nil
		}
	}
	return fmt.Errorf("task with id %d not found", id)
}

3️⃣ Menambahkan Unit Test untuk Repository

File: internal/repository/task_repository_test.go

Untuk memastikan repository kita bekerja dengan benar, kita buat serangkaian unit test menggunakan library testify/assert.

go
package repository

import (
	"testing"
	"time"

	"github.com/fardannozami/golang-todolist-cli/internal/model"
	"github.com/stretchr/testify/assert"
)

var tasks = []model.Task{
	{
		Id:          1,
		Description: "Learn Golang",
		CreatedAt:   time.Now(),
	},
	{
		Id:          2,
		Description: "Write Unit Tests",
		CreatedAt:   time.Now(),
	},
}

func TestNewInMemoryTaskRepository(t *testing.T) {
	repo := NewInMemoryTaskRepository()
	assert.NotNil(t, repo)
	assert.Empty(t, repo.tasks)
}

func TestInMemoryTaskRepository_AddTask(t *testing.T) {
	repo := NewInMemoryTaskRepository()
	for _, task := range tasks {
		err := repo.AddTask(task)
		assert.NoError(t, err)
	}
	assert.Len(t, repo.tasks, len(tasks))
	assert.Equal(t, tasks, repo.tasks)
}

func TestInMemoryTaskRepository_GetAll(t *testing.T) {
	repo := NewInMemoryTaskRepository()
	for _, task := range tasks {
		_ = repo.AddTask(task)
	}
	result, err := repo.GetAllTasks()
	assert.NoError(t, err)
	assert.Len(t, result, len(tasks))
	assert.Equal(t, tasks, result)

	// Pastikan hasil GetAll merupakan salinan (bukan referensi langsung)
	result[0].Description = "Modified"
	assert.NotEqual(t, result[0].Description, repo.tasks[0].Description)
}

func TestInMemoryTaskRepository_DeleteTaskById(t *testing.T) {
	repo := NewInMemoryTaskRepository()
	for _, task := range tasks {
		_ = repo.AddTask(task)
	}

	err := repo.DeleteTaskById(1)
	assert.NoError(t, err)
	assert.Len(t, repo.tasks, 1)
	assert.Equal(t, 2, repo.tasks[0].Id)

	err = repo.DeleteTaskById(999)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "task with id 999 not found")
}

func TestInMemoryTaskRepository_MarkTaskAsCompleted(t *testing.T) {
	repo := NewInMemoryTaskRepository()
	for _, task := range tasks {
		_ = repo.AddTask(task)
	}

	err := repo.MarkTaskAsCompleted(1)
	assert.NoError(t, err)
	assert.NotNil(t, repo.tasks[0].CompletedAt)
	assert.Nil(t, repo.tasks[1].CompletedAt)

	err = repo.MarkTaskAsCompleted(999)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "task with id 999 not found")
}

Untuk menjalankan test, cukup gunakan perintah:

bash
go test ./internal/repository

✅ Kesimpulan

Di seri ini kita telah:

* Membuat model Task

* Mengimplementasikan InMemoryTaskRepository

* Menambahkan unit test lengkap untuk setiap metode repository

Ini adalah pondasi penting sebelum kita masuk ke bagian interaktif (command line interface) di seri berikutnya.


Sampai jumpa di bagian ketiga! 🚀

Article Series

Golang Todolist CLI

Lanjutkan membaca seri ini untuk melihat perjalanan lengkapnya.

  1. 1
    Aplikasi Todo (CLI)
    8 Mei 20252 min read
  2. 2
  3. 3
  4. 4