-
Notifications
You must be signed in to change notification settings - Fork 196
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
Dependency injection for user defined resources #4930
Comments
I can offer an idea for a solution, though I haven't thought through all of the edge cases. To recap, suppose I have a class implemented once for each platform: class BucketAws {
// fun amazon logic
}
class BucketAzure {
// fun microsoft logic
} Writing these classes works well. If I want the classes to share some logic, I could have class Bucket {
new(props) {
if util.env("WING_TARGET") == "tf-aws" {
this.inner = new BucketAws(props);
} else if util.env("WING_TARGET") == "tf-azure" {
this.inner = new BucketAzure(props);
} else {
throw "unsupported target";
}
}
inflight put(key: str, value: str): void {
return this.inner.put(key, value);
}
} I think an ergonomic solution to this would be if I could just specify on each class which platform it's implemented for: class Bucket for "tf-aws" {
// fun amazon logic
}
class Bucket for "tf-azure" {
// fun microsoft logic
} Boom. Whenever But there's a catch -- how do we ensure the One idea is if there's multiple classes of the same name with different "for" clauses, the compiler will eagerly type check both of them and then validate that they all have the same public API signatures (including their constructors). If there's a discrepancy, you'd get an error in all corresponding locations: class Bucket for "tf-aws" {
// error: "tf-aws", "tf-azure" implementations of class "Bucket" have different signatures for "put"
inflight put(key: str): void {}
}
class Bucket for "tf-azure" {
// error: "tf-aws", "tf-azure" implementations of class "Bucket" have different signatures for "put"
inflight put(key: str, value: str): void {}
} Since you haven't specified a common interface for Bucket up front, the compiler would be pretty strict in this scenario. If there's a public method named But let's say you wanted more flexibility. Then Wing could let you define an abstract class up front: abstract class Bucket {
new(...BucketProps);
inflight put(key: str: value: str): void;
} Now, the compiler only needs to check that |
I like the pub abstract class Bucket {
pub someField: number
new(props: ...BucketProps) {
this.someField = 100;
}
protected sharedFunctionality() {
// Will probably be common to actually implement functions in abstract classes
}
inflight abstract put(key: str: value: str): void;
} Then model the linkage with normal inheritance (with all the benefits and drawbacks), with the added twist of class Bucket extends cloud.Bucket for "tf-aws" {
// inherited constructor and members
// errors if no abstract members are implemented
inflight put(key: str: value: str): void {
// ...
}
} This implies of course that you'd be allowed to do |
i like the
|
We are assuming that In this case, Wing novelty is the fact that you can call |
Notes from a recent design discussion:
|
Hi, This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. |
@Chriscbr @hasanaburayyan what's the status of this? Any chance we can get this prioritized... Feels like we are accumulating lots a lot of debt in winglibs resulting from this missing capability. |
If we want to take more time to plan out the syntax sugar for this, a potential interim solution could be to support writing Wing platforms in Wing, then could instead have (Not that Im advocating for pushing this off just thinking outloud) But Im all for prioritizing this and just moving forward with the |
I think the problem is that |
Just wanted to share an observation I had related to the challenge of supporting extending classes (after recently implementing #6490). When someone extends a concrete class, it's normal that within most OO languages, inside the child class implementation, you're able to access the parent's implementation (fields, methods) as long as they're accessible (either public or protected). But if you're extending an abstract (in Wing, this means dependency-injectable) class, you don't necessarily know the implementation of the class you're extending. So it's important that the Wing compiler doesn't let you do something weird like this: class Bucket for "tf-aws" {
pub field1: str;
new() {
this.field1 = // ...
}
}
class Bucket for "tf-azure" {
pub field2: num;
new() {
this.field2 = // ...
}
}
// who are we extending? an abstract "Bucket" or one of the concrete "Bucket" classes above?
class SuperBucket extends Bucket {
new() {
super();
this.field1 = "hey"; // error: field1 might not exist (???)
}
} I think the design suggested by Mark where you're required to define some abstract class probably makes the most sense for addressing this ambiguity. In that case, the above code would just error as soon as it's discovered that there are two pub abstract class Bucket {
pub field1: number = 0;
}
class BucketTfAws extends cloud.Bucket for "tf-aws" {
new() { this.field1 = 3; }
}
class BucketTfAzure extends cloud.Bucket for "tf-azure" {
new() { this.field1 = 5; }
}
pub class SuperBucket extends Bucket {
new() {
super();
this.field1 *= 2;
}
} |
Today, Wing has mechanism that allows us to define and inject concrete Wing SDK resources based on the target platform defined during compilation.
When authoring user defined classes today, users use a hacky pattern where they use a proxy type and then explicitly check for
WING_TARGET
in their code.As an example, check out https://github.com/winglang/winglibs/blob/main/containers/workload.w
Now that we are starting to write more winglibs, such as postgres, the lack of an official way to express this in wing is starting to impact more and more users.
We need a better way.
The text was updated successfully, but these errors were encountered: