Skip to content

Modularizing Assemblies

Hok Shun Poon edited this page Feb 11, 2015 · 38 revisions

Typhoon allows you to group related components together under a TyphoonAssembly sub-class.

  • Keep things organized and group related parts of an application assembly.
  • Expose an interface containing the public component(s) to be produced. The internal details need not be exposed.
  • Allow plugging in different implementations of an assembly when [activating](Activating assemblies).

How do I make one assembly use components from another?

While grouping components together in logical, modular TyphoonAssembly units is great, they're not very useful if they are all isolated from each other.

Let's say you have separate TyphoonAssemblys for each of your application's three main concerns: UI (UIAssembly), network operations (NetworkComponents) and persistence (PersistenceComponents).

In NetworkComponents you might have a httpClient that handles RESTful API calls, like so:

@implementation NetworkComponents

- (id<SignUpClient>)httpClient
{
    return [TyphoonDefinition withClass:[HttpClient class] 
        configuration:^(TyphoonDefinition* definition)
    {
        //etc. . . 
    }];
}

@end

Now let's suppose we have a signUpViewController in UIAssembly that takes a new user's credential information and needs httpClient to submit the information onto the backend. No problem:

@interface UIAssembly : TyphoonAssembly

// Typhoon will automatically inject the two collaborating assemblies (NetworkComponents and PersistenceComponents) here.
@property(nonatomic, strong, readonly) NetworkComponents* networkComponents;
@property(nonatomic, strong, readonly) PersistenceComponents* persistenceComponents;

// Local components that require components from collaborating assemblies ... 
- (RootViewController *)rootViewController;

- (SignUpViewController *)signUpViewController;

- (StoreViewController *)storeViewController;
@end

@implementation UIAssembly

// signUpViewController consumes httpClient component from 'NetworkComponents'

- (SignUpViewController *)signUpViewController
{
    return [TyphoonDefinition withClass:[SignUpViewController class] 
        configuration:^(TyphoonInitializer* initializer)
    {        
        [definition injectProperty:@selector(client) with:[_networkComponents httpClient]];
    }];
}

... // Other UIAssembly components here

@end

Viola.

NOTE: all collaborating assemblies need to be [activated](Activating assemblies) — just like any other assembly — for this to work. Otherwise, Typhoon will simply fail to resolve references to their components when you run the application.

###Activating

We can provide a different realization of the NetworkComponents or PersistenceComponents when [activating the assembly](Activating Assemblies) as long as the objects they build conform to the same class or protocol.

TyphoonComponentFactory* factory = [[TyphoonBlockComponentFactory alloc] 
    initWithAssemblies:@[
        [UIAssembly assembly],
        [TestNetworkComponents assembly],
        [PersistenceComponents assembly]
    ]];
  
SignUpViewController* viewController = [(UIAssembly*) factory signUpViewController];


##Layered architecture

Modules can really help to promote a neat, robust architecture. But without a little discipline its still easy to get things messed up. Try to avoid heavy coupling between your modules. A good rule-of-thumb is to aim for a layered architecture, as shown below. We have infrastructure components at the bottom, with increasing levels of abstraction until we reach our top-level application assembly. Visibility between the layers can point downwards, preferably to the layer immediately below, but should not point upwards.

Typhoon


See also: The The Typhoon Sample Application for an example showing the use of modularized assemblies.

See also: TyphoonPatcher, as described in Integration Testing is another approach to swapping out components for another implementation.