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

Feat: Record serialization/deserialization #1706

Merged
merged 58 commits into from
Sep 16, 2024
Merged

Feat: Record serialization/deserialization #1706

merged 58 commits into from
Sep 16, 2024

Conversation

milaGGL
Copy link
Contributor

@milaGGL milaGGL commented Jun 13, 2024

Fixes #1152
Contribution by @eranl: #1223
Internal link for details: b/363272745

@product-auto-label product-auto-label bot added size: xl Pull request size is extra large. api: firestore Issues related to the googleapis/java-firestore API. labels Jun 13, 2024
@milaGGL milaGGL changed the title Mila/test record Feat: Record serialization/deserialization Jun 13, 2024
Copy link

generated-files-bot bot commented Aug 28, 2024

Warning: This pull request is touching the following templated files:

  • .github/workflows/ci.yaml

@milaGGL milaGGL marked this pull request as ready for review September 11, 2024 16:17
@milaGGL milaGGL requested review from a team as code owners September 11, 2024 16:17
@tom-andersen tom-andersen self-assigned this Sep 11, 2024
Copy link
Contributor

@tom-andersen tom-andersen left a comment

Choose a reason for hiding this comment

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

Looks good.

My comments are mostly about internal location of code. I feel code is being split across many files, and this has caused some duplication, decrease in encapsulation and code that jumps between files more. I think this could be simplified.

If this can be cleaned up a little now, that would be good. But I don't consider this a blocker to merging. We can followup on this in future PRs if you like.

// Helper class to convert from maps to custom objects (Beans), and vice versa.
private static class BeanMapper<T> {
private final Class<T> clazz;
private static class PojoBeanMapper<T> extends BeanMapper<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since BeanMapper and RecordMapper exist in separate files, I think we should also move the PojoBeanMapper from being an inner class to also being a top level class in it's own file as well.

I think previously, the serialization/deserialization logic was contained in CustomClassMapper, but now is spread across many files. This is the Java way of doing things, with separate files for separate classes. But we should maybe consider having a folder to contain all these classes, maybe named serialization.

I would like to see some refactoring to:

  • Better encapsulate logic. Many methods have had their private attribute removed, which I take as indication to logic being placed in the wrong place. The BeanMapper class could be a better place for common logic, since methods can be protected here.
  • Remove duplication. For example:
      if (serverTimestamps.contains(property) && propertyValue == null) {
        // Replace null ServerTimestamp-annotated fields with the sentinel.
        serializedValue = FieldValue.serverTimestamp();
      } else {
        serializedValue = CustomClassMapper.serialize(propertyValue, path.child(property));
      }

Could be pushed up into parent BeanMapper somehow.

  • Some more logic from CustomClassMapper should likely be moved over to DeserializeContext. For example, since ErrorPath lives in DeserializeContext, the serializeError and deserializeError should maybe move over there as well now, perhaps even as methods on the ErrorPath class.

Copy link
Contributor Author

@milaGGL milaGGL Sep 12, 2024

Choose a reason for hiding this comment

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

  • move PojoBeanMapper to separate class
  • move serialization related files into encoding folder (keeping consistent with database)
    -- Note: this will make CustomClassMapper public to be accessible by other files in firestore. Marked "internalApi" to avoid public use.
  • "Many methods have had their private attribute removed" -> so far, custom class mapper only have serialize and deserializeToType methods changed from private to default, this is acceptable.
  • move duplictated code to BeanMapper
  • move serializeError and deserializeError to DeserializeContext

Copy link
Contributor

Choose a reason for hiding this comment

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

The BeanMapper class could be a better place for common logic, since methods can be protected here.

I agree. Having mapper subclasses call an external utility class is less modular.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

make sense

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove the record folder, and have this in the parent folder instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. Also removed duplicated code from the file

@eranl
Copy link
Contributor

eranl commented Sep 11, 2024

Looks good.

My comments are mostly about internal location of code. I feel code is being split across many files, and this has caused some duplication, decrease in encapsulation and code that jumps between files more. I think this could be simplified.

If this can be cleaned up a little now, that would be good. But I don't consider this a blocker to merging. We can followup on this in future PRs if you like.

@tom-andersen, @milaGGL, let me know if you'd like me to take a stab at these comments, either pre- or post-merge.

@milaGGL
Copy link
Contributor Author

milaGGL commented Sep 11, 2024

Looks good.
My comments are mostly about internal location of code. I feel code is being split across many files, and this has caused some duplication, decrease in encapsulation and code that jumps between files more. I think this could be simplified.
If this can be cleaned up a little now, that would be good. But I don't consider this a blocker to merging. We can followup on this in future PRs if you like.

@tom-andersen, @milaGGL, let me know if you'd like me to take a stab at these comments, either pre- or post-merge.

hi @eranl, I agree with Tom's comments and prefer to address them pre-merge. I will work on the comments tomorrow, but you are very much welcome to help out if you would like to.

@eranl
Copy link
Contributor

eranl commented Sep 12, 2024

hi @eranl, I agree with Tom's comments and prefer to address them pre-merge. I will work on the comments tomorrow, but you are very much welcome to help out if you would like to.

Sure. Let me know how I can help.

@@ -38,7 +43,7 @@
import java.util.concurrent.ConcurrentMap;

/** Helper class to convert to/from custom POJO classes and plain Java types. */
class CustomClassMapper {
public class CustomClassMapper {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it a good idea to make this class and the methods below public, exposing them to library users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am also hesitating over this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added "@internalapi" to avoid public use.

@@ -93,18 +90,6 @@ abstract T deserialize(
Map<TypeVariable<Class<T>>, Type> types,
DeserializeContext context);

void ensureValidDocumentIdType(String fieldDescription, String operation, Type type) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why move these methods from base class to a utility class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am moving out all util methods that are not specific to the base class or used in both child classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and this is renamed to validateDocumentIdType to match validateServerTimestampType method.

@milaGGL
Copy link
Contributor Author

milaGGL commented Sep 13, 2024

Have done some code re-format. Please review @tom-andersen, @eranl.

@milaGGL milaGGL requested a review from eranl September 13, 2024 16:19
Comment on lines 104 to 110
if (constructor == null) {
throw context.errorPath.deserializeError(
"Class "
+ getClazz().getName()
+ " does not define a no-argument constructor. If you are using ProGuard, make "
+ "sure these constructors are not stripped");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This check is redundant, since this class can't be instantiated with a null constructor. Also, it's not a no-argument constructor, but the record's canonical one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct, the constructor should not be null.


documentReference.set(ALL_SUPPORTED_TYPES_OBJECT).get();

CommitRequest expectedCommit = commit(set(ALL_SUPPORTED_TYPES_PROTO));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these files are only compile-able with JDK16+ anyway, why not use var?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Explicitly writing out the types is more readable and maintainable. Clarity is preferred over simplicity.

@eranl
Copy link
Contributor

eranl commented Sep 16, 2024

Looks good. Thanks for pushing this through.

@milaGGL milaGGL merged commit f5613b4 into main Sep 16, 2024
24 checks passed
@milaGGL milaGGL deleted the mila/test-record branch September 16, 2024 17:06
milaGGL added a commit that referenced this pull request Sep 18, 2024
milaGGL added a commit that referenced this pull request Sep 18, 2024
@milaGGL milaGGL restored the mila/test-record branch September 20, 2024 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: firestore Issues related to the googleapis/java-firestore API. size: xl Pull request size is extra large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for serializing java record types
5 participants