-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate ecocredits module #153
Conversation
Codecov Report
@@ Coverage Diff @@
## master #153 +/- ##
==========================================
+ Coverage 65.31% 65.68% +0.37%
==========================================
Files 27 28 +1
Lines 1508 1501 -7
==========================================
+ Hits 985 986 +1
+ Misses 420 413 -7
+ Partials 103 102 -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks okay but left a few comments
} | ||
|
||
return &classInfo, nil | ||
return &classInfo, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like an unnecessary change. Result should be nil if there's an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? If there is an error, then the result by definition in Go SDK is not specified.
I find it not useful to have the additional 3-4 lines of code just of an err check when the error can be returned directly. I found that google and other Go companies are using this patter across different libraries. Here example in Std lib: https://golang.org/src/strconv/atof.go line 559.
} | ||
|
||
return &ecocredit.QueryBatchInfoResponse{Info: &batchInfo}, nil | ||
return &ecocredit.QueryBatchInfoResponse{Info: &batchInfo}, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
x/ecocredit/server/server.go
Outdated
batchInfoTable orm.NaturalKeyTable | ||
} | ||
|
||
func newServer(storeKey sdk.StoreKey) serverImpl { | ||
// Server is the the ecocredits implementation of ADR 031 Msg Service | ||
type Server interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this is desirable. I was trying to not expose this outside of RegisterServices. If we need an interface just inherit MsgServer and QueryServer rather than writing out each method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It helps with testing. The pattern I'm following is to return interfaces wherever possible and following the excellent proverb:
“Accept interfaces, return structs”
There are many benefits from it, especially if we design different components and will like to do unit-tests. It's also part of SOLID principles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you are right, we should reuse MsgServer
and QueryServer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right I was just wanting to make the serverimpl private and force people to use clients
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests all use clients so they don't need this public
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for context I actually did exactly what you're doing first btw 😉 but then changed to make it private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. BTW: interface, not an implementation. But probably you are right - with the comments above, we won't need to expose anything about the server (neither the combined interface). I will look at it tomorrow again, meanwhile I added CLI code (query is done, Tx only one command).
I found that we are recreating a server and hardcoding it in local functions (eg in SDK modules in all handler methods). This leads to difficulties or weird designs if we want to test or customize it. Instead, I propose to relay more on tests and configurability. In this commit, I compose a Server inside the AppModule. The AppModule constructor could take a server as a parameter, however I decided to not do it and not expose the default functionality. Module test suites however, will like to customize it. They can do that by creating an AppModule themselves - the struct is public.
Is it useful to do all this stylistic refactoring? I thought the goal was to wire things up and add cli commands not just reorganize stuff a bit and honestly I don't see how this reorganization improves things. |
x/ecocredit/module/module.go
Outdated
|
||
"github.com/regen-network/regen-ledger/x/ecocredit" | ||
"github.com/regen-network/regen-ledger/x/ecocredit/server" | ||
) | ||
|
||
type AppModule struct { | ||
Key sdk.StoreKey | ||
Srv server.Server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What value is there in adding this here? Where is it going to need to be publicly used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact the main use-case was to use them in handlers. Since we don't need to implement a Router, then it has less utility - app module doesn't need to hold the server instance any more (at least I don't see other methods where it's needed, RegisterGRPCGatewayRoutes
also doesn't use the server).
The only reason I to keep it here is to be able to customize the server within the AppModule.RegisterServices
. Since the AppModule has much less utility now, then probably this is not needed. We can parametrize the package ecocredits/RegisterServices
method (note, it's not in the server anymore, and it will take Server as an argument) and external tests can use that method instead of AppModule. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the app module doesn't need a server instance at all with this design and that's intentional. Maybe I need to document the reasons more but here's my thinking:
- other modules shouldn't hold "keepers"/servers directly (according to the WIP ADR 033), instead they use clients which handle permissions correctly. So a direct server instance is undesirable because it breaks ocaps/encapsulation
- an
AppModule
can eventually become sort of a "module descriptor" which instantiates module services but doesn't maintain any of its own state. Module descriptors would only contain config parameters. InRegisterServices
, services are constructed and registered but not owned by the app module - rather ownership is transfered to the various routers/service managers.
My preference right now is to leave RegisterServices
in server
as it was. I originally had things the way you have them here, but I changed it because that felt like it had better encapsulation. Basically with RegisterServices
in server
, tests don't need to know how services are getting implemented or ever have a concrete server implementation - all of that is hidden so there is proper ocaps provided by the clients.
Does that make sense?
In general I'm leaning in the direction of AppModule being much leaner and tests would mainly need RegisterInterfaces
and RegisterServices
instead of all the other AppModule stuff currently there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking that a better, less high coupled approach might be something like:
- what is
types
in the SDK and now the root in this approach just has types and maybe something like aModuleDescriptor
with justRegisterInterfaces
server
has a lightweightServerModule
which just hasRegisterServices
+RegisterInterfaces
- there are separate client modules wired up separately like
CLIClientModule
,GRPCGatewayClientModule
, etc.
This has a dependency structure where x/{module}/server
and x/{module}/client
depend on x/{module}
and app.go only imports x/{module}/server.ServerModule
, and we don't have everything like clients and simulations getting imported into app.go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense. I like that. Although I would keep the AppModule
name - it describes it's goal very well. x/{module}
shouldn't import x/{module}/subpackage
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me too. Some comments:
- why not call it
ModuleServer
instead ofServerModule
: it's literally a module's server.- clients could be
ModuleCLIClient
,ModuleGRPCGatewayClient
...
- clients could be
- I'm not sure why we have
RegisterInterfaces
in bothServerModule
andModuleDescriptor
. Only once should be enough? My preference goes to the root package. - While
CLIClientModule
andGRPCGatewayClientModule
makes things feel more organized, consistent and structured, it also seems overdone, as e.g.GRPCGatewayClientModule
afaics will only ever have one line like:
ecocredits.RegisterQueryHandlerClient(context.Background(), mux, ecocredits.NewQueryClient(clientCtx))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is not to expose the Server interfaces (I'm removing that interface), and the AppModule / ServerModule is not a server - it provides module information / descriptors.
x/ecocredit/server/server_test.go
Outdated
RegisterServices(key, configuratorFixture) | ||
s := testsuite.NewIntegrationTestSuite(configuratorFixture) | ||
cfg := configurator.NewFixture(t, []sdk.StoreKey{key}, addrs) | ||
registerServers(key, cfg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto why refactor this? What are you trying to accomplish?
I was planning to merge do CLI in a separate PR. I added Query CLI implementation here, the Tx CLI is work in progress. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The biggest issue is updating the usage strings of the cmd's. Those need just a bit of grammar editing.
I have different ideas about module organization, but I can maybe demonstrate that in a PoC. Otherwise, generally looks okay. Thanks @robert-zaremba .
x/ecocredit/client/query.go
Outdated
func queryClassInfo() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "class_info [class_id]", | ||
Short: "Select a class info", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short: "Select a class info", | |
Short: "Retrieve credit class info", |
x/ecocredit/client/query.go
Outdated
|
||
Args: cobra.ExactArgs(1), | ||
Use: ecocredit.ModuleName, | ||
Short: "Querying commands for the ecocredit module", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short: "Querying commands for the ecocredit module", | |
Short: "Query commands for the ecocredit module", |
Short: `Creates a new credit class. | ||
designer: address of the account which designed the credit class | ||
issuer: comma separated (no spaces) list of issuer account addresses. Example: "addr1,addr2" | ||
metadata: base64 encoded metadata - arbitrary data attached to the credit class info`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what do you think about using flags --binary-metadata
and --string-metadata
? Often it will be json I imagine
x/ecocredit/client/query.go
Outdated
Short: "Select the credit issuance batch info", | ||
Long: "Select the credit issuance batch info based on the bach_denom (ID)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
x/ecocredit/client/query.go
Outdated
Short: "Select the tradable and retired balance of the credit batch", | ||
Long: "Select the credit tradable and retired balance of the credit batch denom (ID) and account address", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
x/ecocredit/client/query.go
Outdated
Short: "Select the maximum length of the fractional part of credits in the given batch", | ||
Long: "Select the maximum length of the fractional part of credits in the given batch. The precision tells what is the minimum unit of a credit.\nExample: a decimal number 12.345 has fractional part length equal 3. A precision=5 means that the minimum unit we can trade is 0.00001", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
x/ecocredit/client/query.go
Outdated
Short: "Select the tradable and retired supply of the credit batch", | ||
Long: "Select the tradable and retired supply of the credit batch batch denom (ID)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
|
||
func (a AppModule) RegisterGRPCGatewayRoutes(sdkclient.Context, *runtime.ServeMux) {} | ||
|
||
func (a AppModule) RegisterInvariants(sdk.InvariantRegistry) {} | ||
|
||
func (a AppModule) RegisterServices(cfg module.Configurator) { | ||
ecocredit.RegisterMsgServer(cfg.MsgServer(), a.Srv) | ||
ecocredit.RegisterQueryServer(cfg.QueryServer(), a.Srv) | ||
key := sdk.NewKVStoreKey(ecocredit.ModuleName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key cannot be created in RegisterServices
. Then it won't get mounted with the multistore.
…obert/ecocredits-integrate
Closes: #134