Skip to content

Architecture : using compositions and resolvers? #1

Description

@KevinJump

at its heart uSync has a collection of Serializers, that are responsible for getting bits of Umbraco in and out of XML. In the current version these are loaded by a singleton at start up and then accessed when needed.

Looking at Umbraco 8 and the best way to do this I think using Compositions and A Collectionbuilder is the way to go - so then we can build a collection like so:

// register *all* serializers, except those marked [HideFromTypeFinder]
composition.WithCollectionBuilder<USyncSerializerCollectionBuilder>()
     .Add(() => composition.TypeLoader.GetTypes<IUSyncSerializer>());

Then in theory when we need to we can go off and get the serializer we require - and this is where it gets funky.

each serializer will serialize a type of umbraco object (e.g IContentType) and so the return type of the functions in the Interface will change based on the type.

Ideally we want to do this with generics - so each serializer would impliment the Serializer class based on their type.

    public interface ISyncSerializer<TObject>
        where TObject : IEntity
    {
        SyncAttempt<XElement> Serialize(TObject item);
        SyncAttempt<TObject> Deserialize(XElement node, bool force);

        bool IsCurrent(XElement node);
    }

So for example...

   [USyncSerializer("B3F7F247-6077-406D-8480-DB1004C8211C", "ContentTypeSerializer")]
    public class ContentTypeSerializer : USyncSerializerBase<IContentType>, ISyncSerializer<IContentType>
    {
        public Type UmbracoObjectType => typeof(IContentType);

        public override SyncAttempt<XElement> Serialize(IContentType item)
        {
            throw new NotImplementedException();
        }

        public override SyncAttempt<IContentType> Deserialize(XElement node, bool force)
        {
            throw new NotImplementedException();
        }

        public override bool IsCurrent(XElement node)
        {
            throw new NotImplementedException();
        }
    }

Except ...

there are few issues i haven't resolved yet ...

  • We can't use composition.TypeLoader.GetTypes to load a generic interface ??
  • Even if we do load them our Collection Builder is of LazyCollectionBuilderBase I am not sure we can load a generic type their either.
  • We could have a non generic Interface but then it becomes a pain when we pass the object about (is this a big performance hit ? as we will be converting all over the code?)
  • We could load based on the Attribute ? TypeLoader.GetAttributedTypes but insider our collectionBuilder ? what do we say the type is because that needs a non-generic type?

At the moment - this is all that is in this repo - see : https://github.com/KevinJump/uSync8/tree/master/uSync8.Core/Serialization for details so far

Activity

  1. zpqrtbnk commented on Jan 20, 2019

    @zpqrtbnk

    First question: nope, TypeLoad.GetTypes does not support getting an open generic interface such as ISyncSerializer<> - we could probably enhance it to support that case, though, but I'd rather first discuss the use case.

    You want to get a collection of serializers. Let's forget about DI, v8, etc for a while. You're going to handle that collection as an enumerable of something, right? C# will not let you have an IEnumerable<ISyncSerializer<>> because ISyncSerializer<> is not a concrete type. So, what should it be? It has to be something that all serializers would share, and it has to be a concrete C# type.

    You could do:

    public interface ISyncSerializer
    { }
    
    public interface ISyncSerializer<T> : ISyncSerializer
    { ... }
    

    So then, all your serializers could fit in an IEnumerable<ISyncSerializer>. And then I assume you'll want... to pick the right serializer for the object you're serializing (for instance, pick a/the ISyncSerializer<IContent> to serialize an IContent object?

    Again, I don't think this has to do with DI or v8 and more with C# code and handling generics.

    What would your serialization loop look like?

  2. KevinJump commented on Jan 20, 2019

    @KevinJump
    OwnerAuthor

    Thanks for the reply 👍

    I think you are right - I might have been thinking about it the wrong way round.

    The serializers are in the most part called by 'handlers' that are the things listening for the internal umbraco events on items (save, delete etc). So they (the handlers) just need to be able to call the serializer for the type they are listening to.

    So super simple that looks a bit like :

    Save(IContentType item) 
    {
        var xml = serializer.Serialize(item);
        xml.Save(path);
    }
    

    In that sense a handler only need be initialized with the serializer it cares about ?

    So that might be ..

    public class ContentTypeHandler :  ISyncHandler
    {
     private ISyncSerializer<IContentType> serializer;
    
     public ContentTypeHandler(ContentTypeSerializer serializer)
     {
         this.serializer = serializer;
     }
    }
    

    Going this way do I just register each serializer in the composition, or can i still load them as a collection?

    But: (and i think this is where i got caught up in my head) I do want a bit of flexibility, because we might want to swap a serializer out (so have a different ContentTypeSerializer loaded at runtime). - So Ideally don't want to construct the Handlers with Specific Serializers hardwired.
    (I might just be trying to build to much flexibility in here, and if the need arises later on, i could refactor the code for this)

    Handlers

    I think the Handlers are simpler
    they are a collection - (& they can have a concrete type), so can be loaded into a collection quite easily.

    Then for example they are initialized to listen to all the events in a Component :

    public void Initialize()
    {
         foreach(var syncHandler in syncHandlers)
         {
            syncHandler.InitializeEvents();
         }
    }
    

    There are other things like when a user asks for a full export - but it's the same loop, so i think i understand that bit.

  3. zpqrtbnk commented on Jan 20, 2019

    @zpqrtbnk

    You can define

    public interface ISerializer {}
    
    public interface ISerializer<T> : ISerializer
    {
      XmlElement Serialize(T item);
    }
    

    Then, you can implement a serializer for content types

    public class ContentTypeSerializer : ISerializer<IContentType>
    {
      XElement Serialize(IContentType item)
      { ... }
    }
    

    and/but inject the interface

    public class ContentTypeHandler :  ISyncHandler
    {
     private ISyncSerializer<IContentType> _serializer;
    
     public ContentTypeHandler(ISerializer<IContentType> serializer)
     {
        _serializer = serializer;
      }
    }
    

    Now you would register:

    composition.Register<ISerializer<IContentType>, ContentTypeSerializer>();
    

    So we're injecting the interface, your handler does not know about ContentTypeSerializer, and you can register a different implementation if needed. Making sense?

  4. KevinJump commented on Jan 22, 2019

    @KevinJump
    OwnerAuthor

    Hi,

    Yet again you are right 😄 - I was getting tied up in dependency stuff and forgot about the interfaceness of it all.

  5. zpqrtbnk commented on Jan 22, 2019

    @zpqrtbnk

    Aha happy to help, ping me if you have more questions. We're supposed to make things simpler, not horribly complex ;-)

  6. KevinJump commented on Jan 22, 2019

    @KevinJump
    OwnerAuthor

    thanks, for the record i think you are- I am loving the Composition / Component stuff it makes loads of sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions