Skip to content

Instantly share code, notes, and snippets.

@ne-sachirou
Last active August 26, 2024 09:14
Show Gist options
  • Save ne-sachirou/be88c43cead2e689ea248ab82c426bfc to your computer and use it in GitHub Desktop.
Save ne-sachirou/be88c43cead2e689ea248ab82c426bfc to your computer and use it in GitHub Desktop.
函數型ドメインモデリング 第12章 永續化 Go
package main
import (
"context"
"errors"
"github.com/ne-sachirou/dmmf-go/query"
)
type EmailAddress string
func NewEmailAddress(emailAddress string) (EmailAddress, error) {
return EmailAddress(emailAddress), nil
}
type PhoneNumber string
func NewPhoneNumber(phoneNumber string) (PhoneNumber, error) {
return PhoneNumber(phoneNumber), nil
}
type ContactInfo interface{}
type Email struct {
ContactInfo
EmailAddress EmailAddress
}
type Phone struct {
ContactInfo
PhoneNumber PhoneNumber
}
type Contact struct {
ContactId int64
Info ContactInfo
}
type ContactRecord struct {
ContactId int64
EmailAddress *string
PhoneNumber *string
}
var InvalidContactRecord = errors.New("invalid contact record")
func ContactRecordToDomain(dbRecord ContactRecord) (Contact, error) {
var info ContactInfo
if dbRecord.EmailAddress != nil {
emailAddress, err := NewEmailAddress(*dbRecord.EmailAddress)
if err != nil {
return Contact{}, errors.Join(InvalidContactRecord, err)
}
info = Email{EmailAddress: emailAddress}
} else if dbRecord.PhoneNumber != nil {
phoneNumber, err := NewPhoneNumber(*dbRecord.PhoneNumber)
if err != nil {
return Contact{}, errors.Join(InvalidContactRecord, err)
}
info = Phone{PhoneNumber: phoneNumber}
} else {
return Contact{}, InvalidContactRecord
}
return Contact{
ContactId: dbRecord.ContactId,
Info: info,
}, nil
}
var ContactNotFound = errors.New("contact not found")
var MultipleContactsFound = errors.New("multiple contacts found")
// Gen/GORM を想定
func ReadOneContact(ctx context.Context, c query.Contact, contactId int64) (Contact, error) {
var records []ContactRecord
records, err := c.WithContext(ctx).Where(c.ContactId.EQ(contactId)).Find()
if err != nil {
return Contact{}, err
}
if len(records) == 0 {
return Contact{}, ContactNotFound
}
if len(records) > 1 {
return Contact{}, MultipleContactsFound
}
return ContactRecordToDomain(records[0])
}
// Gen/GORM を想定
func WriteContect(ctx context.Context, c query.Contact, contact Contact) error {
record := ContactRecord{
ContactId: contact.ContactId,
}
switch info := contact.Info.(type) {
case Email:
record.EmailAddress = (*string)(&info.EmailAddress)
case Phone:
record.PhoneNumber = (*string)(&info.PhoneNumber)
default:
return errors.New("invalid contact info")
}
if err := c.WithContext(ctx).Create(&record); err != nil {
return err
}
if err := c.WithContext(ctx).Where(c.ContactId.EQ(contact.ContactId)).Update(record.EmailAddress, record.PhoneNumber); err != nil {
return err
}
return nil
}
package main
import (
"context"
"errors"
"time"
"github.com/ne-sachirou/dmmf-go/query"
)
type Customer struct {
CustomerID int64
Name string
Birthdate *time.Time
}
type CustomerRecord struct {
CustomerID int64
Name string
Birthdate *string
}
var InvalidCustomerRecord = errors.New("invalid customer record")
func CustomerRecordToDomain(dbRecord CustomerRecord) (Customer, error) {
var birthdate *time.Time
if dbRecord.Birthdate == nil {
birthdate = nil
} else {
b, err := time.Parse("2006-01-02", *dbRecord.Birthdate)
if err != nil {
return Customer{}, errors.Join(InvalidCustomerRecord, err)
}
birthdate = &b
}
return Customer{
CustomerID: dbRecord.CustomerID,
Name: dbRecord.Name,
Birthdate: birthdate,
}, nil
}
var CustomerNotFound = errors.New("customer not found")
var MultipleCustomersFound = errors.New("multiple customers found")
// Gen/GORM を想定
func (c query.Customer) ReadOneCustomer(ctx context.Context, customerId int64) (Customer, error) {
var records []CustomerRecord
records, err := c.WithContext(ctx).Where(c.CustomerID.EQ(customerId)).Find()
if err != nil {
return Customer{}, err
}
if len(records) == 0 {
return Customer{}, CustomerNotFound
}
if len(records) > 1 {
return Customer{}, MultipleCustomersFound
}
customer, err := CustomerRecordToDomain(records[0])
if err != nil {
return Customer{}, err
}
return customer, nil
}
module github.com/ne-sachirou/dmmf-go
go 1.23.0
require gorm.io/gorm v1.25.11
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/text v0.14.0 // indirect
)
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
package main
func main() {
}
package main
import (
"context"
"gorm.io/gorm"
"github.com/ne-sachirou/dmmf-go/query"
)
type Invoice interface{}
type UnpaidInvoice struct {
Invoice
InvoiceId int64
}
type PaidInvoice struct {
Invoice
InvoiceId int64
}
type InvoicePaymentResult interface{}
type FullyPaid struct {
InvoicePaymentResult
}
type PartiallyPaid struct {
InvoicePaymentResult
Invoice Invoice
}
func isFullyPaid(paymentResult InvoicePaymentResult) bool {
_, ok := paymentResult.(FullyPaid)
return ok
}
type PayInvoiceCommand struct {
InvoiceId int64
Payment int64
}
func postInvoicePaidEvent(invoiceId int64) {}
func applyPayment(unpaidInvoice Invoice, payment int64) InvoicePaymentResult {
// なあにこれ?
updatedInvoice := applyPayment(unpaidInvoice, payment)
if isFullyPaid(updatedInvoice) {
return FullyPaid{}
} else {
return PartiallyPaid{Invoice: updatedInvoice}
}
}
type Service interface {
LoadUnpaiedInvoiceFromDatabase(ctx context.Context, q query.Invoice, invoiceId int64) (UnpaidInvoice, error)
MarkAsFullyPaidInDb(ctx context.Context, q query.Invoice, invoiceId int64) error
MarkPaymentCompleted(ctx context.Context, q query.Payment, paymentId int64) error
UpdateInvoiceInDb(ctx context.Context, q query.Invoice, invoice Invoice) error
}
var db *gorm.DB
// Gen/GORM を想定
func PayInvoice(ctx context.Context, payInvoiceCommand PayInvoiceCommand, service Service) error {
q := query.Use(db)
return q.Transaction(func(tx *query.Query) error {
invoiceId := payInvoiceCommand.InvoiceId
unpayedInvoice, err := service.LoadUnpaiedInvoiceFromDatabase(ctx, tx.Invoice, invoiceId)
if err != nil {
return err
}
payment := payInvoiceCommand.Payment
paymentResult := applyPayment(unpayedInvoice, payment)
switch r := paymentResult.(type) {
case FullyPaid:
if err := service.MarkAsFullyPaidInDb(ctx, tx.Invoice, invoiceId); err != nil {
return err
}
if err := service.MarkPaymentCompleted(ctx, tx.Payment, invoiceId); err != nil {
return err
}
postInvoicePaidEvent(invoiceId)
case PartiallyPaid:
if err := service.UpdateInvoiceInDb(ctx, tx.Invoice, r.Invoice); err != nil {
return err
}
}
return nil
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment