You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
During the redesign of ASP.Net, extensibility was one of the major design tenets. This is evident by the heavy use of interfaces, injection, factories, and the level of decomposition. As the project has progressed, the strategy has shifted much more closely to one of a closed framework in order for the core team to maintain control and agility of the implementation. This is evident by the number of internal types. The team is now headed towards restructuring the Engineering Guidelines to express this change in position.
Impact on architecture
Because of the shift in stance over time, the architecture arguably no longer aligns with the needs of the framework. An extensible design is much more complicated to reason about and to maintain. In the current state, many of the original extensibility patterns are unusable, but are still being imposed on the framework.
To give a specific example, let's consider DefaultViewCompilerProvider and DefaultViewCompiler. These implement IViewCompilerProvider and IViewCompiler respectively. A provider is responsible for constructing and returning some compiler instance. In this case, the DefaultViewCompilerProvider collects precompiled views and constructs an instance of DefaultViewCompiler to return. The purpose of this design pattern is to decouple the construction from the implementation. One of the main ways to leverage this pattern is to create a variety of providers that construct your implementation in different ways. However, here we find that DefaultViewCompiler is marked as internal. That makes it impossible to create some alternative IViewCompilerProvier that constructs the DefaultViewCompiler differently. The effect is that the provider and implementation are tightly coupled in that this provider is the only provider that can return this implementation. In cases of forced coupling like this, the compiler should simply construct itself, instead. Splitting out the provider in these cases only convolutes the architecture without any benefit.
The heavy use of these extensibility patterns are a burden on the framework. It makes it more difficult to follow, understand, and maintain the framework. If the stance of the framework is to restrict extensibility, then the architecture would benefit by removing the heavy extensibility patterns and move to a much simpler design.
Impact on users
Extensibility is extremely difficult. There are three main paths that a user can take:
Compel the framework team that the functionality they need is valuable enough to the community at large that a first-class extensibility point / feature for their specific need is worth adding to the product. This is a difficult and slow process with a relatively low probability of success (there are too many needs with too few team members to support them all).
Go ahead and implement the interfaces you need given the extensibility points the framework offers. Because of the heavy use of internals, even small changes rapidly balloon out into having to rebuild large segments of the framework. For example, if you want to simply change the values DefaultViewCompilerProvider uses to construct a default implementation of DefaultViewCompiler, you have to also provide your own implementation of the internal DefaultViewCompiler. In order to do something simple, like filter the set of precompiled views the default view compiler considers, you suddenly have to provide your own implementation for the entire compiler and all of it's dependencies.
Deal with it as-is. Sometimes this is not an option, depending on the functionality the users needs. In some cases, the user can opt to take on hacky or convoluted workaround to get by. If they were to the point of considering extensibility in the first place, it likely means the burden of a workaround is highly undesirable.
Impact on team focus
This situation represents a tug-of-war between the core team trying to maintain control of the framework and users trying to take control of their applications. Because the team cares highly about users, the more control the team takes over the framework, the more time they end up spending tending to the needs of users' applications. Spoiler alert, the team is not big enough to adequately support all of the users' applications themselves.
Without a robust set of extensibility options, the backlog is flooded with requests to add specialized features. In sacrificing robust extensibility options, the core team has gained control of the framework, but is consequently stuck with the responsibility of implementing peripheral features themselves instead of being able to focus on the core framework. The team has the heavy burden of constantly deciding which user needs are 'worthy' and which users must be abandoned.
We could probably find an interesting correlation between the proportion of feature requests received in relation to the level of extensibility offered by a framework.
Discussion Points
What is the true stance of the framework on extensibility? Should it be a highly open, extensible framework, or should it be a tightly controlled, closed framework? More accurately, how far left or right should the slider be on the extensibility scale. We must be carful to consider all of the impacts of this decision. On the surface, it may appear to be an advantage for the core team to maximize framework agility. However, once all of the impacts of that decision are weighed, it may ultimately hinder the team's ability to move the framework forward.
How frequently does the team intend to make significant changes to the framework such that they can benefit from favoring agility? One could argue that the closer the framework gets to being stable / feature complete, the closer it should get to maximizing extensibility.
How well does the current architectural precedence align with the needs of the framework? If there are no advantages to the original extensibility patterns, why do we continue to carry forward the burden of them? Can / should it be simplified?
Is there a better balance? Are there better patterns that would improve the extensibility picture while also allowing the framework to protect certain types from taking on explicit user dependencies?
Should all 'depths' of the framework have the same stance on extensibility, or should it vary (e.g. the deeper down, the more protected)?
Should the Engineering Guidelines provide guidance on the level of extensibility and the correlating design patterns that best align with the framework's needs in different circumstances?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
During the redesign of ASP.Net, extensibility was one of the major design tenets. This is evident by the heavy use of interfaces, injection, factories, and the level of decomposition. As the project has progressed, the strategy has shifted much more closely to one of a closed framework in order for the core team to maintain control and agility of the implementation. This is evident by the number of
internal
types. The team is now headed towards restructuring the Engineering Guidelines to express this change in position.Impact on architecture
Because of the shift in stance over time, the architecture arguably no longer aligns with the needs of the framework. An extensible design is much more complicated to reason about and to maintain. In the current state, many of the original extensibility patterns are unusable, but are still being imposed on the framework.
To give a specific example, let's consider
DefaultViewCompilerProvider
andDefaultViewCompiler
. These implementIViewCompilerProvider
andIViewCompiler
respectively. A provider is responsible for constructing and returning some compiler instance. In this case, theDefaultViewCompilerProvider
collects precompiled views and constructs an instance ofDefaultViewCompiler
to return. The purpose of this design pattern is to decouple the construction from the implementation. One of the main ways to leverage this pattern is to create a variety of providers that construct your implementation in different ways. However, here we find thatDefaultViewCompiler
is marked asinternal
. That makes it impossible to create some alternativeIViewCompilerProvier
that constructs theDefaultViewCompiler
differently. The effect is that the provider and implementation are tightly coupled in that this provider is the only provider that can return this implementation. In cases of forced coupling like this, the compiler should simply construct itself, instead. Splitting out the provider in these cases only convolutes the architecture without any benefit.The heavy use of these extensibility patterns are a burden on the framework. It makes it more difficult to follow, understand, and maintain the framework. If the stance of the framework is to restrict extensibility, then the architecture would benefit by removing the heavy extensibility patterns and move to a much simpler design.
Impact on users
Extensibility is extremely difficult. There are three main paths that a user can take:
Compel the framework team that the functionality they need is valuable enough to the community at large that a first-class extensibility point / feature for their specific need is worth adding to the product. This is a difficult and slow process with a relatively low probability of success (there are too many needs with too few team members to support them all).
Go ahead and implement the interfaces you need given the extensibility points the framework offers. Because of the heavy use of internals, even small changes rapidly balloon out into having to rebuild large segments of the framework. For example, if you want to simply change the values
DefaultViewCompilerProvider
uses to construct a default implementation ofDefaultViewCompiler
, you have to also provide your own implementation of the internalDefaultViewCompiler
. In order to do something simple, like filter the set of precompiled views the default view compiler considers, you suddenly have to provide your own implementation for the entire compiler and all of it's dependencies.Deal with it as-is. Sometimes this is not an option, depending on the functionality the users needs. In some cases, the user can opt to take on hacky or convoluted workaround to get by. If they were to the point of considering extensibility in the first place, it likely means the burden of a workaround is highly undesirable.
Impact on team focus
This situation represents a tug-of-war between the core team trying to maintain control of the framework and users trying to take control of their applications. Because the team cares highly about users, the more control the team takes over the framework, the more time they end up spending tending to the needs of users' applications. Spoiler alert, the team is not big enough to adequately support all of the users' applications themselves.
Without a robust set of extensibility options, the backlog is flooded with requests to add specialized features. In sacrificing robust extensibility options, the core team has gained control of the framework, but is consequently stuck with the responsibility of implementing peripheral features themselves instead of being able to focus on the core framework. The team has the heavy burden of constantly deciding which user needs are 'worthy' and which users must be abandoned.
We could probably find an interesting correlation between the proportion of feature requests received in relation to the level of extensibility offered by a framework.
Discussion Points
What is the true stance of the framework on extensibility? Should it be a highly open, extensible framework, or should it be a tightly controlled, closed framework? More accurately, how far left or right should the slider be on the extensibility scale. We must be carful to consider all of the impacts of this decision. On the surface, it may appear to be an advantage for the core team to maximize framework agility. However, once all of the impacts of that decision are weighed, it may ultimately hinder the team's ability to move the framework forward.
How frequently does the team intend to make significant changes to the framework such that they can benefit from favoring agility? One could argue that the closer the framework gets to being stable / feature complete, the closer it should get to maximizing extensibility.
How well does the current architectural precedence align with the needs of the framework? If there are no advantages to the original extensibility patterns, why do we continue to carry forward the burden of them? Can / should it be simplified?
Is there a better balance? Are there better patterns that would improve the extensibility picture while also allowing the framework to protect certain types from taking on explicit user dependencies?
Should all 'depths' of the framework have the same stance on extensibility, or should it vary (e.g. the deeper down, the more protected)?
Should the Engineering Guidelines provide guidance on the level of extensibility and the correlating design patterns that best align with the framework's needs in different circumstances?
Beta Was this translation helpful? Give feedback.
All reactions