Skip to content

Modularizing Assemblies

Jasper Blues edited this page Mar 1, 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 TyphoonAssembly<PersistenceComponents>) here.
@property(nonatomic, strong, readonly) NetworkComponents* networkComponents;

//If you wish, collaborating assemblies can be backed by a protocol. Here we 
//declare the type as TyphoonAssembly<MyProtocol> to indicate to Typhoon
//that this is a collaborating assembly. In the app's own classes no further
//coupling to Typhoon is necessary, and we may declare a property as type 
//id<MyProtocol> 
@property(nonatomic, strong, readonly) TyphoonAssembly<PersistenceComponents> persistenceComponents;


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

- (SignUpViewController *)signUpViewController;

- (StoreViewController *)storeViewController;
@end

And now to use a definition from another assembly . . .

@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.

###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. This gives plenty of flexibility, for example, to patch out the network components for stubs in integration tests.

For each collaborating assembly that is declared, one that fulfills this role must be provided during activation, or Typhoon will fail to assemble the component.

UIAssembly *uiAssembly = [UIAssembly assembly];
NetworkComponents *networkComponents = [TestNetworkComponents assembly];
PersistenceComponents *persistenceComponents = [PersistenceComponents assembly];

[[TyphoonAssemblyActivator withAssemblies:@[
    uiAssembly, 
    networkComponents, 
    persistenceComponents]] activate];
  
SignUpViewController* viewController = [uiAssembly 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 generally should not point upwards. (There can be occasional exceptions to this, for example in the case of delegates).

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.