Skip to content
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

Merged
merged 17 commits into from
Nov 10, 2020
Merged

Integrate ecocredits module #153

merged 17 commits into from
Nov 10, 2020

Conversation

robert-zaremba
Copy link
Collaborator

@robert-zaremba robert-zaremba commented Nov 5, 2020

Closes: #134

  • Created Server interface
  • adding server to app module
  • implementing Route

@robert-zaremba robert-zaremba marked this pull request as draft November 5, 2020 16:13
@codecov
Copy link

codecov bot commented Nov 5, 2020

Codecov Report

Merging #153 (5bb66b2) into master (c1e2235) will increase coverage by 0.37%.
The diff coverage is 85.71%.

@@            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     

Copy link
Member

@aaronc aaronc left a 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
Copy link
Member

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

Copy link
Collaborator Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

batchInfoTable orm.NaturalKeyTable
}

func newServer(storeKey sdk.StoreKey) serverImpl {
// Server is the the ecocredits implementation of ADR 031 Msg Service
type Server interface {
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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

Copy link
Member

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

Copy link
Member

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

Copy link
Member

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

Copy link
Collaborator Author

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.
@aaronc
Copy link
Member

aaronc commented Nov 5, 2020

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.


"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
Copy link
Member

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote about it on Discord and in the commit message: ff2a903

The whole idea is to allow modules to make it dependency: other modules can mock a server if needed.

Copy link
Collaborator Author

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?

Copy link
Member

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:

  1. 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
  2. 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. In RegisterServices, 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.

Copy link
Member

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 a ModuleDescriptor with just RegisterInterfaces
  • server has a lightweight ServerModule which just has RegisterServices + 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.

Copy link
Collaborator Author

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.

Copy link
Contributor

@amaury1093 amaury1093 Nov 6, 2020

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 of ServerModule: it's literally a module's server.
    • clients could be ModuleCLIClient, ModuleGRPCGatewayClient...
  • I'm not sure why we have RegisterInterfaces in both ServerModule and ModuleDescriptor. Only once should be enough? My preference goes to the root package.
  • While CLIClientModule and GRPCGatewayClientModule 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))

Copy link
Collaborator Author

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/module/module.go Outdated Show resolved Hide resolved
RegisterServices(key, configuratorFixture)
s := testsuite.NewIntegrationTestSuite(configuratorFixture)
cfg := configurator.NewFixture(t, []sdk.StoreKey{key}, addrs)
registerServers(key, cfg)
Copy link
Member

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?

x/ecocredit/module/module.go Outdated Show resolved Hide resolved
@robert-zaremba
Copy link
Collaborator Author

I was planning to merge do CLI in a separate PR. I added Query CLI implementation here, the Tx CLI is work in progress.

@robert-zaremba robert-zaremba marked this pull request as ready for review November 5, 2020 23:36
Copy link
Member

@aaronc aaronc left a 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 .

func queryClassInfo() *cobra.Command {
return &cobra.Command{
Use: "class_info [class_id]",
Short: "Select a class info",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Short: "Select a class info",
Short: "Retrieve credit class info",


Args: cobra.ExactArgs(1),
Use: ecocredit.ModuleName,
Short: "Querying commands for the ecocredit module",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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`,
Copy link
Member

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/module/module.go Outdated Show resolved Hide resolved
Comment on lines 55 to 56
Short: "Select the credit issuance batch info",
Long: "Select the credit issuance batch info based on the bach_denom (ID)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment on lines 74 to 75
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment on lines 112 to 113
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment on lines 93 to 94
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)",
Copy link
Member

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)
Copy link
Member

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.

@aaronc aaronc merged commit 00ac858 into master Nov 10, 2020
@ryanchristo ryanchristo deleted the robert/ecocredits-integrate branch July 2, 2022 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[x/ecocredit] wire up ecocredit module to app.go
3 participants