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

Make public functions of Project type immutable #518

Merged
merged 2 commits into from
Jan 10, 2024

Conversation

glours
Copy link
Collaborator

@glours glours commented Dec 27, 2023

⚠️ Breaking changes ⚠️

What I did

  • Add github.com/mitchellh/copystructure to make deep copy of Project struct
  • Make all Project public functions which mutate Project state immutabl, returning a new instance of Project and usage a common With prefix for each of them
  • Replace the Project.WithProject function by a Project.ForEachProject function
  • Remove the unused types.set type

Related issue
https://docker.atlassian.net/browse/COMP-380

@glours glours force-pushed the immutability-refactoring branch 5 times, most recently from c5a8d95 to 4182b0a Compare December 29, 2023 09:47
@glours glours changed the title Make public function of Project type immutable Make public functions of Project type immutable Dec 29, 2023
@glours glours self-assigned this Dec 29, 2023
@glours glours marked this pull request as ready for review December 29, 2023 15:32
@glours glours requested a review from ndeloof as a code owner December 29, 2023 15:32
@glours glours requested review from milas and ndeloof and removed request for ndeloof December 29, 2023 15:33
types/project.go Outdated
return newProject, nil
}

func (p *Project) projectDeepCopy() (*Project, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not just deepCopy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

useless gif

Copy link
Member

@milas milas left a comment

Choose a reason for hiding this comment

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

LGTM, some comments but nothing majorly concerning.

Two other high-level thoughts:

  1. mitchellh is no longer doing Go dev - he recently archived a bunch of libs; I'm not super concerned since deep copying is an internal detail and doesn't pollute the API, more of an FYI

  2. The naming & arguments of all these functions is pretty inconsistent, since this is all breaking API changes anyway, it might make sense to do a pass for consistency, e.g. make them all WithFoo() style.

types/project.go Outdated
func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) error {
// WithServices runs ServiceFunc on each service and dependencies according to DependencyPolicy
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) (*Project, error) {
Copy link
Member

Choose a reason for hiding this comment

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

This method is the only one I think is odd - the returned project option is always identical to the original.

I think it might make sense to have a pair of functions:

  • ForEach(func(name string, svc ServiceConfig) error) error - functional loop helper, does not copy project
  • MutateServices(func(name string, svc *ServiceConfig) error) (*Project, error) - clone project, allowing caller to get a mutable reference to each service in the process

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agree. As we now use WithXX fr mutating functions, this name is not relevant anymore. ForEach would be a better one. We then can have a new WithServiceFn(func(ServiceConfig) ServiceConfig) for service mutation, if relevant (afaik we don't use this in docker/compose)

types/project.go Outdated
func (p *Project) DisableService(service ServiceConfig) {
// DisableService removes from the project model the given service and its references in all dependencies
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) DisableService(service ServiceConfig) (*Project, error) {
Copy link
Member

Choose a reason for hiding this comment

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

minor: it's kind of weird that this one takes a ServiceConfig instead of a name like all the others (but that's not new)

types/project.go Outdated
Comment on lines 627 to 630
instance, err := copystructure.Copy(p)
if err != nil {
return nil, err
}
Copy link
Member

Choose a reason for hiding this comment

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

In theory, this should not fail, right? It might make sense for this to panic() to avoid carrying the error everywhere.

types/project.go Outdated
func (p *Project) ForServices(names []string, options ...DependencyOption) error {
// ForServices restricts the project model to selected services and dependencies
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) ForServices(names []string, options ...DependencyOption) (*Project, error) {
Copy link
Member

Choose a reason for hiding this comment

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

(see other comment re: getting rid of error from deepCopy())

I am wondering if it makes sense to ignore unknown names here, so that this method can return *Project, which would make it much more ergonomic to use: p = p.ForServices(svc).

In particular, this could allow chaining, e.g.

p.ForServices(svcs).ForEach(...)

Copy link
Collaborator Author

@glours glours Jan 9, 2024

Choose a reason for hiding this comment

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

Or we can introduce an Either type 😇 , WDYT?
edit: maybe a bad idea, this kind of structure isn't used in the Golang ecosystem.

Copy link
Collaborator

@ndeloof ndeloof Jan 9, 2024

Choose a reason for hiding this comment

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

I am wondering if it makes sense to ignore unknown names here

this would be a bug then if we silently ignore incorrect parameter being set, especially if this is "only" for code convenience.

@ndeloof
Copy link
Collaborator

ndeloof commented Jan 9, 2024

mitchellh is no longer doing Go dev

.. which is sad as we rely on mapstructure for parsing. Maybe we should revisit this to only rely on yaml.Unmarshall - unfortunately go-yaml is not open to such hybrid approach and my PR didn't received much attention.
We could implement deepClone without such a reflexion framework, by plain hand-written clone code

@milas
Copy link
Member

milas commented Jan 9, 2024

Yeah...at least mapstructure does seem to be maintained with recent commits and is very popular. In either case, I'm not super worried – we can also always fork them (or move to a community fork) if the need arises

@glours glours requested review from ndeloof and milas January 10, 2024 09:39
@glours glours force-pushed the immutability-refactoring branch 3 times, most recently from 5ac1ba3 to 06666e2 Compare January 10, 2024 13:36
Copy link
Member

@milas milas left a comment

Choose a reason for hiding this comment

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

lgtm!

types/project.go Outdated Show resolved Hide resolved
@milas
Copy link
Member

milas commented Jan 10, 2024

I fixed the DCO status and set the option in the repo settings so it doesn't happen again with accepting PR comments:

Screenshot 2024-01-10 at 11 20 11 AM

@glours
Copy link
Collaborator Author

glours commented Jan 10, 2024

I fixed the DCO status and set the option in the repo settings so it doesn't happen again with accepting PR comments:

Screenshot 2024-01-10 at 11 20 11 AM

Arff I just push force without pulling your changes my bad 🤦

Edit: Humm as we use a copy of each service now, I think we don't need the comment anymore in fact

Signed-off-by: Guillaume Lours <[email protected]>
@ndeloof ndeloof merged commit 3d88bd1 into compose-spec:main Jan 10, 2024
8 checks passed
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.

3 participants