-
Notifications
You must be signed in to change notification settings - Fork 130
Refactoring PHP generation to use the MVVM pattern #317
Refactoring PHP generation to use the MVVM pattern #317
Conversation
This was prototyped in googleapis#268 for both Java and PHP; the present PR only includes PHP and shared stuff. The MVVM pattern uses the following steps: 1. The Model is converted to a ViewModel, which is a set of classes containing the data to be used when rendering the templates. 2. The snippet engine renders the output using only data from the ViewModel structure. Other changes with the MVVM model: * There is no context object when generating under the MVVM model. All data must be prepared beforehand. * No casing changes are done in templates; all identifiers must be composed and cased correctly inside the view model. There are a number of new utility classes created: * Name: represents an identifier name which is casing-aware. * TypeName: Represents a simple or complex type and keeps track of the aliases for the contributing types. * TypeTable: manages the imports for a set of fully-qualified type names. * SurfaceNamer: provides language-specific names or other strings. There is also a change unrelated to MVVM: * Using TreeSet for auth scopes to give a consistent ordering
* ApiMethodTransformer generates view objects from method definitions. | ||
*/ | ||
public class ApiMethodTransformer { | ||
private InitCodeTransformer initCodeTransformer; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
addressed comments by either making changes or responding to them - PTAL |
*/ | ||
package com.google.api.codegen.viewmodel; | ||
|
||
public interface ParamView {} |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
* @throws IllegalArgumentException if any of the strings contain any characters that are not | ||
* lower case or underscores. | ||
*/ | ||
public static Name from(String... pieces) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
} | ||
Doc doc = snippetSetRunner.generate(surfaceDoc); | ||
if (doc == null) { | ||
continue; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -48,15 +49,15 @@ public GeneratedResult generate( | |||
ImmutableMap.<String, Object>of("context", context)); | |||
|
|||
String outputFilename = snippets.generateFilename(element).prettyPrint(); | |||
PhpContextCommon phpContextCommon = new PhpContextCommon(); | |||
PhpTypeTable phpTypeTable = new PhpTypeTable(); | |||
|
|||
// TODO don't depend on a cast here | |||
PhpContext phpContext = (PhpContext) context; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Are the context methods like |
@@ -1207,7 +1213,7 @@ class LibraryServiceApi | |||
* try { | |||
* $libraryServiceApi = new LibraryServiceApi(); | |||
* $formattedName = LibraryServiceApi::formatBookName("[SHELF]", "[BOOK]"); | |||
* $indexName = ""; | |||
* $indexName = "default index"; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
A general comment is that the This could wait until the Java PR, since I understand that there might be some active shuffling around of these fields right now. |
There are a surprising number of public methods in |
These fields aren't likely to shuffle. Since the fields are simple POJOs, for the most part, any documentation would be a conversion from the camel case of the identifier into noun phrase form. Additional details would probably only talk about where they are used, which would only refer to other code, which isn't much of a value-add. My hope is that the naming of the fields is transparent such that they don't need documentation.
I will convert a few more methods to private that are only needed within their own class. In general, as I argued in the previous PR, I don't think making things package private buys us very much in the context of toolkit where no one is depending on us. I would prefer to do a more thorough pass of access levels when we want to convert into a library for others to consume & extend. |
Made some updates, PTAL |
I don't think I ever responded to that. I think that in any large project, we should use the lowest level of access possible for just about everything (with the possible exception of constants). It's not just about us vs. them; it's also about what we can hold in our head at any given time, and it's helpful to see that the compiler enforces a limited interface between a model and the rest of the application. We don't need to wait for a thorough audit to start moving in that direction now. Code cleanups, documentation, unit testing, etc. can and should happen incrementally as we do our other features. |
@@ -230,6 +228,7 @@ class LibraryServiceApi | |||
return self::$archivedBookNameTemplate; | |||
} | |||
|
|||
|
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
I wouldn't count on that. In some cases it's obvious, but in many cases it isn't. You're very familiar with this code in a way that others are not. Additional details could take the form of code examples in one or two languages, to show what it represents in concrete terms. This does provide value-add that an IDE cannot easily do for you, because it grounds the abstract concepts in the viewmodel to make them easily understandable for other developers who don't yet have the mental model that you do. Like I said, I won't hold up this PR for documenting these classes, but I would like to see this improved in the near future while it's still fresh in your mind. A refactoring is a good opportunity to improve our game in terms of things like documentation and unit testing. |
I understand the idea of using the most restrictive visibility. The problem here is that the MVVM feature cuts across quite a few packages, all of which need to interact. There are components that I would ideally like to hide, but which need to be public because the need to be accessed in another package of MVVM. For example, the language specific transformers (which are in a different package as per the architectural restructuring suggested by you & Shin) need to access at least one public method in each of the general transformers. Thus, every transformer needs to be public. A good language feature would be to allow multiple packages to be part of a single visibility scope, but alas, we have no such thing. (C# seems to have an "internal" visibility level which limits visibility to an "assembly", which is close to what I want). |
Okay, I think we're on the same page with this. Java unfortunately has no true concept of sub-packages, unlike Scala or C#. I think there might be ways to whittle down the visibility given that everything in the language-specific packages are subclasses of the common transformer types, but we can defer that argument until after this PR. |
Cool. I do want to note that for the key interaction I mentioned, between PhpGapicSurfaceTransformer and *Transformer in the general package, PhpGapicSurfaceTransformer has no structural relation to the *Transformer types; it only implements the interface ModelToViewTransformer. I have no idea how to give special access to PhpGapicSurfaceTransformer to make calls to the *Transformer types without contortions that would make the code more opaque and harder to maintain. We can discuss after the PR though. |
LGTM. Let's have a 1:1 next week to discuss my remaining concerns around documentation and access modifiers. I'll put something on your calendar. |
This was prototyped in #268
for both Java and PHP; the present PR only includes PHP and shared
stuff.
The MVVM pattern uses the following steps:
containing the data to be used when rendering the templates.
ViewModel structure.
Other changes with the MVVM model:
data must be prepared beforehand.
composed and cased correctly inside the view model.
There are a number of new utility classes created:
aliases for the contributing types.
names.
There is also a change unrelated to MVVM: