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

Refactor: templates #961

Merged
merged 12 commits into from
Apr 14, 2022
Merged

Conversation

matejvasek
Copy link
Contributor

@matejvasek matejvasek commented Apr 11, 2022

Make fn.Template an interface instead of a concrete struct.

Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
@knative-prow knative-prow bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Apr 11, 2022
@codecov
Copy link

codecov bot commented Apr 11, 2022

Codecov Report

Merging #961 (07c0aa4) into main (739cded) will increase coverage by 2.21%.
The diff coverage is 53.76%.

@@            Coverage Diff             @@
##             main     #961      +/-   ##
==========================================
+ Coverage   43.85%   46.06%   +2.21%     
==========================================
  Files          55       57       +2     
  Lines        5163     7454    +2291     
==========================================
+ Hits         2264     3434    +1170     
- Misses       2576     3695    +1119     
- Partials      323      325       +2     
Impacted Files Coverage Δ
cmd/completion_util.go 0.00% <0.00%> (ø)
cmd/invoke.go 34.81% <0.00%> (-0.97%) ⬇️
docker/docker_client.go 82.52% <ø> (+0.57%) ⬆️
k8s/logs.go 0.00% <0.00%> (ø)
k8s/secrets.go 13.54% <0.00%> (-0.75%) ⬇️
s2i/builder.go 32.69% <32.69%> (ø)
invoke.go 52.99% <61.53%> (+2.99%) ⬆️
filesystem.go 64.96% <64.06%> (+5.46%) ⬆️
client.go 61.94% <66.66%> (+2.01%) ⬆️
cmd/build.go 60.49% <70.83%> (+0.86%) ⬆️
... and 58 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bc3a418...07c0aa4. Read the comment docs.

@matejvasek matejvasek changed the title WIP: Templates refactor Refactor: templates Apr 11, 2022
Make `fn.Template` interface not struct.

Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
@matejvasek matejvasek marked this pull request as ready for review April 11, 2022 17:29
@knative-prow knative-prow bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 11, 2022
@matejvasek
Copy link
Contributor Author

@zroubalik @lkingland @salaboy PTAL

@matejvasek matejvasek requested a review from lance April 11, 2022 20:22
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Copy link
Member

@lkingland lkingland left a comment

Choose a reason for hiding this comment

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

I really do like this update very much. I hope my long comments below do not obscure that fact. The improved clarity of copying and ability to use different implementations of a Template is really going to be nice to have (especially Dynamic Templates!)

My suggestions boil down to:

  • Pass data structures by value (unless sharing a memory location is actually needed)
  • (related) Use non-pointer receivers for methods (unless sharing a memory location is actually needed)
  • (related) Use non-pointer arguments and return new data structures with mutations applied rather than ask a method to go mutate a memory location
  • Rely on Template.Write to decide whether or not to mask manifest.yaml, rather than masking it from the filesystem.
  • Defaults should be applied in constructors (where used), with everywhere else failing fast
  • Tests should use the API where possible and avoid hard-coding a specific cases or relying on a side-effect (where possible)
  • Directly serialize Repository, Runtime and Template out at different levels rathern than using a shared nested config (high field overlap, but they are not the same!).
  • Comments are a pain, but should really be expected, and follow the Go style of "all exported members have at least a sentence, beginning with the exported token".

/lgtm
/hold for others and for potentially update which address these above gentle suggestions

Overall looks good!

templates.go Show resolved Hide resolved
template.go Outdated Show resolved Hide resolved
filesystem.go Outdated Show resolved Hide resolved
template.go Outdated Show resolved Hide resolved
template.go Outdated
return t.repository + "/" + t.name
}

func (t *template) Write(ctx context.Context, f *Function) error {
Copy link
Member

@lkingland lkingland Apr 11, 2022

Choose a reason for hiding this comment

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

Here also, to receive a Function as a reference is not really necessary since it is a data structure. Generally preferable to omit the pointer and pass by value unless its methods are clearly intended to mutate its internal state at a shared memory location.

As an example, here, simply calling .Write and passing it a *Function causes the passed function to have its state mutated. That is a side-effect and generally an antipattern unless the visitor pattern is clearly expected and required.

In the case where this really is what's desired, it even then is often better for a method to accept an immutable struct and return a new struct with modifications applied.

This is, for example, the logic behind the API of the built-in append. We do not:

append(&elements, "B")

Nor do we:

elements.append("B")

Rather, we:

elements = append(elements, "B")

I elaborate on the pros/cons in the other comment...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lkingland Little bit out of topic: To be honest I have mixed feelings from append.
It doesn't modify slice you are appending to which is good.
However returned slice may or may not share underlying memory.

	s1 := make([]int, 1, 1024)
	s2 := append(s1, 1)

	fmt.Println(s1)
	s2[0] = 42 // it might look like you are not modifying s1 but you might be
	fmt.Println(s1)

It's not working like conj in Clojure where collections are immutable.

filesystem.go Show resolved Hide resolved
repository.go Outdated Show resolved Hide resolved
repository.go Outdated Show resolved Hide resolved

type templateConfig struct {
Copy link
Member

Choose a reason for hiding this comment

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

I think the concept of a templateConfig might be complicating what could really just be one object.

If the reason for separating these fields out is such that their fields can be embedded in Repository and Runtime structs, then I have a whole other long-winded reason why that may not be necessary (tl;dr they share fields, but are not the same because there are indeed small diferences at each level).

This might actualy be simpler and easer kept as a single template struct, with a few duplicated fields as necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

then I have a whole other long-winded reason why that may not be necessary (tl;dr they share fields, but are not the same because there are indeed small diferences at each level).

@lkingland what are those differences?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To me it seems that BuildConfig, HealthEndpoints, BuildEnvs and Invocation are just defaults for fn.Function that can be defined at level of repository runtime, or template.
Is there any objective problem with that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the reason for separating these fields out is such that their fields can be embedded in Repository and Runtime structs,

That's exactly what I want to do in subsequent PR.

@@ -32,9 +30,76 @@ type Template struct {
Invocation Invocation `yaml:"invocation,omitempty"`
}

// template
type template struct {
Copy link
Member

@lkingland lkingland Apr 13, 2022

Choose a reason for hiding this comment

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

If you do take my suggestion and keep template only, by combining bacl in templateConfig, you would just need to capitalize those fields you want serialized to disk (BuildConfig, Invocation)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see comment

@knative-prow knative-prow bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 13, 2022
@knative-prow knative-prow bot added the lgtm Indicates that a PR is ready to be merged. label Apr 13, 2022
@knative-prow
Copy link

knative-prow bot commented Apr 13, 2022

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: lkingland, matejvasek

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [lkingland,matejvasek]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@matejvasek
Copy link
Contributor Author

Pass data structures by value

@lkingland To be clear I am huge fan of referential transparency and immutability whenever possible.

But at least for Write() function if feels phony to pretend it's pure function. It definitely is not pure function.
It already has side-effects -- it writes to filesystem, plus it's named "Write" clearly indicating it mutates things.

If we want to play game of functional programming then we perhaps want to change it to something like:

func (t template) GenerateProject(fn Function) (outFn Function, projectFiles fs.FS, err error) {}

And let caller to do the write to filesystem.

Signed-off-by: Matej Vasek <mvasek@redhat.com>
@knative-prow knative-prow bot removed the lgtm Indicates that a PR is ready to be merged. label Apr 13, 2022
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Signed-off-by: Matej Vasek <mvasek@redhat.com>
@matejvasek
Copy link
Contributor Author

@lkingland I addressed some of your comments:
diff: 99df905...07c0aa4

@matejvasek
Copy link
Contributor Author

@lkingland please re-LGMT

@lkingland
Copy link
Member

/lgtm

@knative-prow knative-prow bot added the lgtm Indicates that a PR is ready to be merged. label Apr 14, 2022
@matejvasek
Copy link
Contributor Author

@zroubalik @salaboy if you don't have objections I'll merge this.

@matejvasek
Copy link
Contributor Author

/unhold

@knative-prow knative-prow bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 14, 2022
@knative-prow knative-prow bot merged commit 34cb893 into knative:main Apr 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants