prisma-client-go icon indicating copy to clipboard operation
prisma-client-go copied to clipboard

Codegen - return interfaces instead of private structs

Open randallmlough opened this issue 1 year ago • 1 comments

Is there a reason codegen returns private structs over exported or interfaces? If these methods could return an interface and or in some instances a generic interface, like the one below, it could open up some cool opportunities.

// prisma-client-go 
// GENERATED FILE

// generic transaction interface
type Tx[T any] interface {
	ExtractQuery() builder.Query
	IsTx()
	Result() T
}

// example of a model returning the Tx interface
func (r activityCreateOne) Tx() Tx[*ActivityModel] {
	v := NewactivityUniqueTxResult()
	v.query = r.query
	v.query.TxResult = make(chan []byte, 1)
	return v
}

By returning an interface specifically on Tx() we can create our own wrapping interface that enables "query builder" methods.

// developers own wrapper interface
type Executor[T any] interface {
	ExtractQuery() builder.Query
	Exec(ctx context.Context) (T, error)
	Tx() prisma.Tx[T]
}

// query builders functions
func buildActivityQuery(db *db.DB, input Input) Executor[*prisma.ActivityModel] {
	return db.Activity.CreateOne(
		prisma.Activity.Title.Set(input.title),
		prisma.Activity.Description.Set(input.description),
		prisma.Activity.StartDate.Set(input.startDate),
		prisma.Activity.Location.Set(input.location),
	)
}

func buildGroupQuery(db *db.DB, input string) Executor[*prisma.GroupModel] {
	return db.Group.CreateOne(
		prisma.Group.Name.Set(input),
	)
}

// example helper functions that either executes the query
func CreateActivity(ctx context.Context, db *db.DB, input Input) (*prisma.ActivityModel, error) {
	act := buildActivityQuery(db, input)
	return act.Exec(ctx)
}

// or create a transaction
func CreateActivityTx(ctx context.Context, db *db.DB, input Input) (prisma.Tx[*prisma.ActivityModel], error) {
	act := buildActivityQuery(db, input)
	return act.Tx(), nil
}

func DoSomethingTheNeedsTransactions(ctx context.Context, db *db.DB, input Input) (*prisma.ActivityModel, error) {
	act, _ := CreateActivityTx(ctx, db, input)
	group := buildGroupQuery(db, "some name").Tx()
	if err := db.Prisma.Transaction(act, group).Exec(ctx); err != nil {
		return nil, err
	}
	return act.Result(), nil
}

// these same query builders can then be used in a mocks expectation
mock.Activity.Expect(
	BuildActivityQuery(client, Input{}),
).Returns(expected)

randallmlough avatar Feb 08 '24 05:02 randallmlough

That's an interesting suggestion. Do you actually have the need in your app to use this?

The reason most of the methods are private is that most users don't need it, and I wanted to really think about the naming and so forth before making these public.

I prepared one small PR to make the transaction results public and it also renames the transaction interface to a better name: https://github.com/steebchen/prisma-client-go/pull/1180.

When I have some more spare time, I might take another look and work on a bigger PR to make this more unified, maybe with generics where it works or makes sense. Thanks for the suggestion and the example code, that's really helpful!

steebchen avatar Feb 14 '24 05:02 steebchen