Faits : A. A._ SA et B._ AG, sont l'une et l'autre actives dans la conception et la réalisation d'installations de chauffage; celle-ci se consacre spécialement aux installations de chauffage au bois. En 2006, la commune de X._ a adjugé à A._ SA l'installation d'un système complet de chauffage avec conduites à distance pour des bâtiments communaux. Le 17 novembre 2006, A._ SA a commandé à B._ AG la livraison et l'installation d'une chaudière à bois destinée aux locaux de l'adjudicatrice; le prix était fixé à 266'716 fr.25, taxes comprises. La chaudière a été mise en service au mois d'octobre 2007. Le 18 de ce mois, B._ AG a adressé à A._ SA une facture de 26'669 fr.30 pour solde du prix de livraison et d'installation de la chaudière. Le 15 mai 2008, elle a réclamé le paiement d'une somme supplémentaire de 860 fr.80 relative à divers travaux complémentaires. Ces factures n'ont pas été acquittées. B. Le 1er décembre 2008, A._ SA a conclu avec la commune de X._ un contrat de maintenance, de conduite et d'entretien des installations de chauffage et de production d'eau chaude de la commune, pour une durée initiale de deux ans. Ce contrat portait notamment sur l'entretien de la chaudière à bois fournie par B._ AG. La rémunération convenue en faveur de A._ SA s'élevait à 18'960 fr. hors taxes, soit 20'400 fr.95 taxes comprises; cette rémunération était indexée. Le 7 juillet 2009, la commune de X._ a averti A._ SA qu'il était nécessaire de procéder à des travaux de réfection du foyer de la chaudière et de mettre cet appareil en conformité avec les directives de la Caisse nationale suisse d'assurance en cas d'accidents (CNA). A._ SA a transmis cette demande à B._ AG le 21 juillet 2009; elle lui a imparti un délai échéant le 1er septembre 2009 pour l'exécution des travaux. Le 12 octobre 2009, la commune de X._ a mis A._ SA en demeure d'exécuter les travaux nécessaires avant le 30 octobre 2009; elle a requis une prolongation de la garantie jusqu'au 30 octobre 2010. Le 29 octobre 2009, B._ AG a accepté d'exécuter les travaux exigés par la Caisse d'assurance mais elle a refusé de prolonger sa garantie parce que la chaudière était déjà en service depuis deux ans. Au sujet de la dégradation du béton réfractaire, elle a indiqué qu'elle allait prendre rendez-vous avec la commune de X._ afin qu'une solution fût trouvée à la satisfaction de toutes les parties. Les représentants de A._ SA, de B._ AG et de la commune de X._ se sont rencontrés le 9 août 2010. Selon le procès-verbal de cette séance, le responsable du service après-vente de B._ AG a déclaré que la durée de vie habituelle d'une installation de chauffage du type de celle installée à X._ atteignait quinze à vingt ans ; il a également admis qu'après trois ans d'utilisation, une usure d'environ 50 mm de la paroi en béton réfractaire présentait un caractère anormal. Les représentants de la commune ont rappelé à A._ SA que la commune n'avait pas de relations contractuelles avec B._ AG et que celle-ci était un sous-traitant. Ils ont invité A._ SA à prendre en charge le coût des travaux de réfection, à exécuter par B._ AG ou par un autre sous-traitant. Le 13 octobre 2010, par l'entremise de son conseil, la commune de X._ a informé A._ SA qu'elle avait instruit directement B._ AG de procéder aux travaux nécessaires et que ceux-ci seraient exécutés aux frais exclusifs de A._ SA. Les travaux de réparation du béton réfractaire ont été exécutés les 25 et 29 octobre 2010. Le 2 décembre suivant, B._ AG a adressé à la commune de Chêne Bougeries une facture de 14'265 fr.55 pour la révision de la chaudière et la remise en état du béton réfractaire. La commune a transmis cette facture à A._ SA avec sommation de l'acquitter avant le 20 mars 2011. A._ SA n'ayant pas donné suite, la commune a finalement versé elle-même le montant réclamé. Le 29 avril 2011, la commune a résilié le contrat de maintenance, de conduite et d'entretien des installations de chauffage conclu avec A._ SA, au motif que celle-ci n'avait pas exécuté ses obligations. C. Le 16 septembre 2011, B._ AG a ouvert action contre A._ SA devant le Tribunal de première instance du canton de Genève. La défenderesse devait être condamnée à payer 34'284 fr.65 et 860 fr.80, avec intérêts au taux de 5% par an, respectivement dès le 18 novembre 2007 et le 15 juin 2008. La défenderesse a conclu au rejet de l'action. Elle a introduit une action reconventionnelle: la demanderesse devait être condamnée à payer 22'848 fr.95 avec intérêts dès le 14 octobre 2009. Cette prétention comprenait une perte de gain chiffrée à 19'293 fr., consécutive à la résiliation du contrat de maintenance et d'entretien par la commune de X._. Le tribunal a interrogé des témoins. Il a notamment interrogé le directeur du marketing de la demanderesse. Celui-ci a déclaré avoir été informé des problèmes de la chaudière de X._. Selon son souvenir, la demanderesse avait un contrat d'entretien; elle avait procédé régulièrement à des révisions et à des mesures d'émissions, ce qui signifiait que l'installation fonctionnait. Le tribunal s'est prononcé le 9 janvier 2014. Accueillant l'action principale, il a condamné la défenderesse selon les conclusions de la demande en justice; il a rejeté l'action reconventionnelle. D. La défenderesse a appelé du jugement pour persister dans l'action reconventionnelle, à concurrence de 19'293 fr. avec intérêts au taux de 5% par an dès le 29 avril 2011. Ces prestations devaient être imputées sur celles obtenues par la demanderesse, lesquelles n'étaient plus contestées. La Chambre civile de la Cour de justice a statué le 26 septembre 2014; elle a rejeté l'appel et confirmé le jugement. E. Agissant par la voie du recours constitutionnel, la défenderesse requiert le Tribunal fédéral de condamner la demanderesse à payer 19'293 fr. avec intérêts au taux de 5% par an dès le 29 avril 2011. Des conclusions subsidiaires tendent à l'annulation de l'arrêt de la Cour de justice et au renvoi de la cause à cette autorité pour nouvelle décision. La demanderesse conclut au rejet du recours. La défenderesse a spontanément déposé une réplique.