goadesign / goa

🌟 Goa: Elevate Go API development! 🚀 Streamlined design, automatic code generation, and seamless HTTP/gRPC support. ✨
https://goa.design
MIT License
5.68k stars 559 forks source link

Don't generate a main() that contains elements of the design #929

Closed nicerobot closed 6 years ago

nicerobot commented 7 years ago

main() should never contain anything generated by the design. By generating content in main() it means after I customize my main() (always, e.g. to add TLS and CLI support), if my design changes i have to copy and paste updates or manually edit changes into my regenerated main().

What should be generated is something like a service.go that contains a func service() *goa.Service {...} which takes over for the body of main().

You can see my example/problem here.

raphael commented 7 years ago

It is not intended for main.go to be re-generated. There are two types of files that goagen generates:

goagen will not override the scaffold files if they already exist. The idea is that these files belong to you, you should maintain them, test them, change them etc. The initial generation is just to help get things started. On the other hand the generated files should not (and cannot) be modified, the tool will clobber the entire generated package contents during each generation.

The bootstrap command - as its name indicates - is supposed to be used when starting a new project, once the project is setup the app, client, swagger etc. commands can be used instead. There are a couple of action items that would help here:

  1. Make the description above clear in the README as it's a constant source of confusion for newcomers (there's a description in the goa-cellar example README but it probably should also be in the main README).
  2. Change the generated go:generate comment to call the individual generators instead of bootstrap as per the above.

I'll leave this issue opened until these are done.

nicerobot commented 7 years ago

Thanks. I understand all that and I do agree that it can be clarified in the docs.

This issue isn't about a misunderstanding. It's about a change to allow for more opportunities for code generation. If i add a new resource to the design, i have to update the controller mountings that are coded into the main. To avoid doing that manually, a separation of that information from main.go will make it easier to regenerate that information. e.g. If i know that i'll never touch service.go manually, i can update the go:generate to always overwrite service.go. And main.go will never be touched with respect to goa because it'll almost always contain customizations.

Let me put it another way. main() is central to ever app. It shouldn't contain anything generated by goa so that it's easier to regenerate that information if the user wants to. But since main() will always be customized, it means the goa-generated information in main() will always have to be maintained manually.

raphael commented 7 years ago

OK thank you for the clarification and sorry for assuming too much :) I have to admit I'm a little bit conflicted on this. On one hand what you are describing would indeed make it easier to re-generate the main code on the other hand it would be promoting a process that in the end might confuse things even more. Today the explanation is roughly: "everything in the main package can be generated once to help you get started then it's all yours", with this change it becomes "everything in the main package is all yours except service.go which you can re-generate by doing goagen main|bootstrap which has a special rule about this file.

Another issue is that the controller constructor functions are often modified to pass references to additional components (database handlers etc.). The code generated by goagen that instantiates and mounts them would thus have to be modified further, defeating the intent.

Finally as you point out the only changes required to main.go have to do with adding or removing calls to the MountXXX functions when the set of design resources change. These changes are simple and don't occur often so at this point leaving the main code generation the way it is seems like the better option.

nicerobot commented 7 years ago

I agree with the concern of confusion. But for a code-generation tool, I lean towards never implementing anything manually that can be generated from the declarative design. In other words, the design must always be the source of truth but it might not be reflected accurately in the main package if allowed to be managed manually. The manual updates can eventually diverge from the declarative design and that should be frowned upon. Maybe my solution isn't ideal but I do think something should be done. Possibly a template solution where the declarative sections are tagged template blocks.

raphael commented 7 years ago

Understood, one additional comment though: there does not have to be a one to one between the resources described in the design and the controllers mounted on a given service. That is why the MountXXX functions exist: to give you the flexibility to mount - or not - a given controller on a given service. There is an interesting pattern which consists of writing a single design that is backed by multiple services. This allows for a single / consistent set of docs and client package / tool but makes it possible to use e.g. CQRS or otherwise split the API implementation into multiple microservices in a way that does not affect clients. So while I agree that the concern of implementation diverging from design is an important one I don't think it applies in this specific case.

nicerobot commented 7 years ago

Let's look at it differently. Regardless of how the services are implemented, I still want to be able to generate the code from goagen and not type the updates manually. If there's a means of generating the code based on the design, even if it's output to stdout, that's better than giving up on the service-generation code after the initial generation.

raphael commented 7 years ago

The problem is that this code cannot be automatically generated. There's an example that the tool produces to help but it's just one way to hook things up which is helpful if you're starting with goa. It may not be the best way for you and certainly isn't the only way. You need to write this code - goa cannot do it for you - for example it doesn't and shouldn't know about all the dependent modules that need to be setup in main and passed down to the controllers.

goa generates the MountXXX functions and the service code must call them providing the implementations of the controller interfaces - that's the contract - and these functions is what has to be (and is) kept up-to-date with the design automatically.

Maybe goagen could generate a TBD.txt file that lists the MountXXX functions and the other responsibilities of the user code such as setting up the security middlewares.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.