Pull to refresh

Comments 74

Да на PHP тоже нужно 3~ недели, если хотите чтоб было по качеству так-же как и у вас. Да и еще и самому протестировать.
Одна строка, может быть и в java, если вызвать консоль с мейл демоном и передать демону нужные параметры, качество оставит желать лучшего.
Ну… Тут же используется модуль. Если для php тоже взять модуль, вроде SwiftMailer, то три недели не нужны.
Бывает. Накипело. Пора создавать клубы анонимных программистов.
С каждым годом вычислительные мощности компьютеров растут, но для компенсации прироста производительности хитрые разработчики придумали фреймворки.
Вот в этой задаче пригодился бы ассемблер!
Не плохо. Но на Django + Celery + django-celery-email + немного своей логики было бы проще. Да, не было бы внутренних SOAP сервисов, мы просто не используем их. Аутентикация на security декораторах. Шаблониатор и прочее как dependency injection подбираются в рантайме. Да, асинхронно. Да, если задача отвалилась, перезапуститься с дефолтным retry=5, иначе ругаться к админу мылом. Да, с аттачами. Да, в аттачах что угодно. Да быстро. Да scalable. Да, буквально 1-2 дня.

Не скажу, что подход на Java как то хуже. Просто он и не лучше. Всего то надо попробовать что-то другое, что бы это увидеть.
При чем тут это? Сложность не в самой отправке, она одной строчкой. Сложность в отказоустойчивости, видимости (каждая задача для Celery видна, можно перезапустить руками или автоматом), удобной интеграции с другими компонентами системы (шаблонизатор, аттачи/документы и т.п.) и удобном использовании компонента, в scalabilty. Так вот это все можно сделать и делают проще. Вместо 3 недель 1-3 дня.
По моему мнению, сравнивать пайтон с джавой некорректно — разный класс. Пайтон можно сравнивать с пхп.
Обоснуйте? У решения есть некоторые качества, такие как корректное поведение под нагрузкой, scalability, корректная обработка всех edge cases, интеграция в другие компоненты (компонент — не одинокий джекичан). Разве важно на каком языке это все было написано?

Другое дело, что архитекура приложения иная. Нет множества SOAP сервисов, что работают вместе и каждый по отдельности это bottle neck, который еще и отдельно нужно скейлить если превышаем нагрузку. Если сервис был написан через пятую точку (более чем реально), то скейлить на несколько машин не получиться, требуется общая VM память/объекты, общие локи и прочие прелести безумного кодинга. В итоге один сервис, джекичан, тянет всех вниз.

На питоне все компоненты работают в одном application server. А вот этих серверов может быть сколько угодно. Байткод не занимает особо памяти, поэтому потребление памяти на один app server небольшое. Впереди серверов стоит один/пара front-end серверов, работающих как буфферы (nginx). Все асинхронные задачи выносятся в Celery/подобное, локов просто нет, все сервера stateless (состояние выносится в базу/Redis/RabbitMQ/по задаче). Celery скейлиться на несколько отдельных машин, если задач много. При желании динамически. Рассылка мыла обычно выносится на отдельный MTA, само приложение об этом даже не знает.

И все это не что-то типичное для питона. То же самое можно построить на Perl, PHP или Java. Вопрос не языке, а в архитектуре всего приложения. И в необходимом времени. Оно все может занять 3 недели, если писать морду на админку, где видны все задачи в микродеталях, маниакально детальная статистика с десятком графиков. Но я этого из статьи не увидел. Да и ортогонально это все как-то основной задаче.
Топ3 решений на пайтоне для энтерпрайза? Их объем рынка?
Спасибо, интересная вещь, нужно будет почитать.
На java тоже есть асинхронный запуск задач (akka, hazelcast), главное- не стрелять из пушек по воробъям.
Soap — он как раз для разнесения серверов и языконезаисимости.
Аутентикация на декоратарах не сильно проще ее же в фильтре будет…
Питон- хороший язык, но я люблю статическое типизирование. Поэтому счас смотрю в сторону Scala…
Вы предлагаете автору переписать проект на Django? :-)
Зачем, сравенние — всегда вещь полезная. Я писал на Django совсем немного (и давно), поэтому мое мнение о нем очень субъективное- по поему он мало-объектоно-ориентирован. Фунциональность добавляется через hook-и. Я предпочитаю template method. То же могу сказать о работе в нем с базой.
На php отправка mail реализуется одной строчкой кода! А на java- нужно 3 недели??!


Java, первая ссылка в гугле (отправка gmail):

public static void Send(final String username, final String password, String recipientEmail, String ccEmail, String title, String message) throws AddressException, MessagingException {
        Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
        final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";

        // Get a Properties object
        Properties props = System.getProperties();
        props.setProperty("mail.smtps.host", "smtp.gmail.com");
        props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.port", "465");
        props.setProperty("mail.smtp.socketFactory.port", "465");
        props.setProperty("mail.smtps.auth", "true");

        props.put("mail.smtps.quitwait", "false");

        Session session = Session.getInstance(props, null);

        // -- Create a new message --
        final MimeMessage msg = new MimeMessage(session);

        // -- Set the FROM and TO fields --
        msg.setFrom(new InternetAddress(username + "@gmail.com"));
        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail, false));

        if (ccEmail.length() > 0) {
            msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(ccEmail, false));
        }

        msg.setSubject(title);
        msg.setText(message, "utf-8");
        msg.setSentDate(new Date());

        SMTPTransport t = (SMTPTransport)session.getTransport("smtps");

        t.connect("smtp.gmail.com", username, password);
        t.sendMessage(msg, msg.getAllRecipients());      
        t.close();
    }


PHP, первая ссылка в Google отправка через gmail:

<?php

       require_once "Mail.php";

        $from = "<from.gmail.com>";
        $to = "<to.yahoo.com>";
        $subject = "Hi!";
        $body = "Hi,\n\nHow are you?";

        $host = "ssl://smtp.gmail.com";
        $port = "465";
        $username = "myaccount@gmail.com";  //<> give errors
        $password = "password";

        $headers = array ('From' => $from,
          'To' => $to,
          'Subject' => $subject);
        $smtp = Mail::factory('smtp',
          array ('host' => $host,
            'port' => $port,
            'auth' => true,
            'username' => $username,
            'password' => $password));

        $mail = $smtp->send($to, $headers, $body);

        if (PEAR::isError($mail)) {
          echo("<p>" . $mail->getMessage() . "</p>");
         } else {
          echo("<p>Message successfully sent!</p>");
         }

    ?>  <!-- end of php tag-->


Вполне сопоставимо по сложности. Преимущество этого Java-кода — он более универсален, можно использовать и для консольных, и для десктопных и для Web. PHP более хардкорный.

Хотя ваша идея абсолютно ясна: задачи с одним и тем же названием могут отличаться по сложности в тысячи и десятки тысяч раз. Мой любимый пример — логгер. Самый простой способ логировать в C# — одна строчка: File.Write (ну 2 строчки, если нужен потокобезопасный). Однако почему то возникла необходимость в создании таких продуктов как log4net и NLog — а там работы на несколько человеко-месяцев.
PHP скрипт читерский — заюзали pear библиотеку
UFO just landed and posted this here
UFO just landed and posted this here
1. Локальный сервер тоже может лежать.
2. SMTP-сервер может просто не справляться с нагрузкой.
3. Не все приложения работают на одном компьютере.
1. Даже если MTA лежит, исходящая почта будет падать в очередь и отправится как только MTA встанет.
2. Как следует из п.1, при любой нагрузке почта теряться не будет, отправится рано или поздно. Ну и предпосылка о том, что удаленный SMTP-сервер уж точно справится с нагрузкой, несколько сомнительна.
3. MTA можно поставить на каждый сервер и указать всем, например, единый смартхост, через который они будут пулять исходящую почту. Абсолютно нормальное решение. Уж точно лучше попытки сделать свой отказоусточивый велосипед :)
UFO just landed and posted this here
Интересное решение.
Но — из триггера в базе уже не отправишь. Так получилось универсально.
К тому же на доталкивание можно логику повесить.
UFO just landed and posted this here
Ну Вы смешали задачи совершенно не относящиеся к отправке почты: сервлеты, веб-сервисы, шаблоны (кстати я использую отдельный шаблон для темы и предпочитаю freemarker). Но, в целом, Вы правы, это не простая задача. Например для Grails я разработал плагин Asynchronous Mail, который позволяет отправлять почту в одну строчку :). Но он тянет за собой 3 плагина, которые тянут 2 фрэймворка.
для ведения промышленной разработки на РНР, также используеся модульная архитектура,
а тем более рассылка — дело довольно-таки не тривиальное… И чтоб сделать толковый модуль и внедрить его в существующую систему, даже используя готовые классы PHPMailrr или Pear потребуется ни одна пара-тройка дней.
Да уж, чего только не придумают, чтобы не набрать

yum install postfix

;-)
И что это дает, кроме инсталляции постфикса?
Вся статья сведется к вызову mail (sendmail) и скармливанию заполненного шаблона. Ну и обертке jax-ws к этому (или сервлету? что там автору надо-то).

И недоступность отработает, и доставку, и очереди. И scalability будет такое, что самопальным наколеночным поделиям и не снилось. А самое смешное — не понадобится ни конфигурация (зачем? на отсылку-то стандартный смартхост из коробки!), ни эм-бинов с БД, ни прочих ентерпрайзиков.

Заодно и узнаете, что mail client это нечто другое ;-)

Посреди многослойной, переплетенной из еаров, сервисов, коннекторов и пулов структуры сервера приложений, органично врастая в сеть модульной архитектуры, выставив наружу красивые интерфейсы, посылая и получая сообщения, отвечая на эрэмай запросы, словно кипящий поток создавая и уничтожая сотни ентити бинов в десятках распределенных транзакций, мудрый сессионный бин срал в лог эксепшенами.


1. Нужна отсылка по вставке записи в базу (BPM создает запись, когда пользователю назначается задание). Как вы ее предлагаете решить через postfix?
2. Окружений у нас — 6 штук. Бывает гораздо больше. Услилия на «самопальное наколеночное поделие»- ксати достаточно простое- очень даже сравнимы на инсталирование MTA (переконфигурирование, поддержание на всех окружениях)
2. Конфигурация в многослойном таком приложенни — есть. Такой факт. Я сторонник держать все ее в одном месте, а не распылять по конфигам. Только если без этого не обойтись. Например- по умолчанию отправка от чъего имени, или суппорт майл — настройки почты, но никак MTA не отдать.
PS: а последняя фраза- красивая, не спорю.
(смотрит) ну давайте мы внезапно еще что-нибудь узнаем про вашу задачу.

Про окружение посмеялся. Да-да, а перенастраивать все остальное как бы не надо. Само заработает.

* * *

У меня для вас есть рассказ — в далеком 1995м году я был такой же молодой и горячий. И тоже рисовал наколеночную отсылку почты, ровно в таком же ентерпрайзненьком стиле. Все было замечательно, пока не поставили этого кадавра клиентам. И тут! Тут началось.

Однажды, в пятницу вечером, то ли гроза выбила сетевушку, то ли уборщица зацепила шваброй провод — но оно таки случилось. И робот по отсылке почты тут же стал слать алерты — ну как же, отвалилось, примите меры. Отсылать было некуда, и все это ровно по вашему рецепту складывалось в БД.

Однако, за субботу с воскресеньем было налеплено столько писем, что на диске закончилось место. БД с логами за него подрались. Поинтересуйтесь, что в этом случае делает MySQL — вам понравится. А поскольку кроме почты БД пользовались и другие — оно все тоже встало колом.

И вот в понедельник с утра — пришел дежурный инженегр. Увидев, что все встало, он подцепил сетевой шнурок. Однако это не помогло. Обнаружив что на диске закончилось место, он удалил что-то ненужное.

Это не конец истории — дальше было вот что. Могучий досылатель, радостно заметив что почтовый сервер появился, тут же «дослал». SMTP там стоял какой-то древний демон на новеле, и от такого подарка он тут же сдох. Ну правильно — теперь на почтовом сервере место закончилось.

Новелевское чудо тоже было в ентерпрайзненьком стиле — и пришлось выгребать помои руками. Сначала из очереди, а потом из мейлбоксов — часть спама таки доехала.

Что же было дальше? А вот что, досылатель снова обнаружил что почтовый сервер работает, и снова дослал новую порцию (да, я был креативным, и не пытался все письма залить сразу. Я разбил на порции. Предусмотрительный был, ага. А то бы сдох кадавр, и проблему решили бы быстрее).

Бам!

В понедельник ближе к ночи была найдена причина этих «бам». Разумеется, это был наш кадавр — но попробуй это определить, когда у тебя серверная далеко не из двух компов состоит, а начальство размахивает руками над ухом.

И вот под утро вторника кадавру почистили очередь руками в базе, не разбираясь — а было ли там хоть что-то полезное, база данных отсвопалась после освобождения места на диске, и все это чудо наконец-то заработало в штатном режиме.

А через месяц вечером к большому боссу клиента пришел старинный приятель. И потребовал чаю. И секретарша, не найдя розетки поближе, выдернула раутер и включила чайник. О ее умственных способностях она узнала только утром. Чайник выдернули, вернули ценный аппарат на место… но кадавр-то не обесточен!

БАМ!!!

Нет, второй раз починили быстрее, к обеду. Кто-то вовремя догадался про могучий досылатель, ведь история была еще свежа в памяти. И вот вместо этого кошерного ентерпрайзика мои старшие товарищи, вспоминая через раз про мои умственные способности, в субботу вечером лепили на коленке простую отсылалку, ориентированную на использование стандартных средств. А я, как самый хитрый, сидел рядом и краснел учился.

В новой инкарнации, отсылатель почты отработал десять лет без единого сбоя. Менялась инфраструктура, сервера и версии софта, но древний монстр, написанный на java 1.0.2, все так же тихо работал на древнючем ультраспарке под не менее древним SunOS.

К слову, 10 лет спустя я был свидетелем демонтажа этого спарка. Единственный раз в жизни я видел слой пыли в системнике, который слежался до такой степени, что из него сразу можно было делать валенки. Первая мысль электронщика была «а зачем в корпус кусок пальто положили?»

Вы говорите — поцфикс не решает? ;-)
Интересная история, спасибо. Тоже посмеялся. Ну и есть чему поучиться на чужих ошибках.
ps: поставлю на аварийную отсылку ограничитель. В уме это конечно было, даже озвучил- но отклика вверху не нашло, поэтому оставил. А тут вижу- не надо лениться.
UFO just landed and posted this here
Не читал, спасибо. Делал я как то SMS сервис (тоже в SOA). Все гораздо сложнее:)
Если это конечно действительно ентерпрайз.
1. Нужно отделить интерфейс от реализации- в разных странах разные провайдепы, да и внутри себя иногда их меняют.
2. Было требование на ограничение по времени и количеству на одного клиента и на систему в целом- (в день- не больше 200, в минуту- не больше 5 например). Причем задаваемое в конфигурации. Не совсем простая задача, когда начинаешь реализовывать.
3. Нужны были счетчики по событиям и статистика. Не меньше этого сервиса получилось:)
Напомнило: В одной из больниц Йоханнесбурга (ЮАР) стали повторяться необъяснимые случаи смерти пациентов после операций — серьезных, но прошедших, казалось бы, вполне успешно. Причем все такие случаи происходили по пятницам.

Расследование показало, что в пятницу в отделение хирургии приходила уборщица. Убирая в реанимационной палате, она ничтоже сумняшеся выдергивала вилку установки «искусственное сердце» из розетки в стене и включала свой пылесос… (http://www.nkj.ru/archive/articles/4083/?sphrase_id=83855)
Интересно, сколько человек вам поверило?
Это достойно отдельного топика -) На хабре нет раздела «байки»? :)
Если известно, что приложение будет исполняться в типичном UNIX-окружении, то, несмотря на обратное утверждение автора статьи, настройки SMTP-сервера можно и захардкодить: 127.0.0.1:25 без авторизации, т.к. в рассматриваемом окружении это всегда правильно. Это уже дело локального почтового сервера переслать письмо через правильный почтовый релей с авторизацией под правильной учетной записью.
Хардкодить в принципе не правильно. Да, можно зарелеить автоматом, но перед отправкой локальный серврер может сбрасывать мыло на диск, чтобы в максимально короткое время принятъ мыло полностью и освободить клиента. А если обзваниваем большую базу клиентов, да с аттачами, это может быть весьма весомо. При криворукости админа система начинает жевать своп, все замедляется тысячи раз, отправка нового мыла заморожена. Если мыло-сервер еще и на одной машине с чем то важным (база, app server, все зависит от фантазии админа), то и эти сервисы замораживаются. Всякие сценарии возможны.
Хотя, все таки нереально это как то все. Извиняюсь за коммент, не могу удалить.
Правильнее все-таки дать возможность настроить smtp, но предусмотреть умолчания — localhost:25 и без пароля.
Если известно, что приложение будет исполняться в типичном UNIX-окружении, то, несмотря на обратное утверждение автора статьи, настройки SMTP-сервера можно и захардкодить: 127.0.0.1:25 без авторизации, т.к. в рассматриваемом окружении это всегда правильно. Это уже дело локального почтового сервера переслать письмо через правильный почтовый релей с авторизацией под правильной учетной записью.

Или тупо вызвать sendmail, который работает (складывает письмо в очередь) даже если собственно smtp-демон в данный момент перезапускается. Лишь бы хватало места и inodes.
Тут поступил приватный коммент от Elmot- некаширно использовать GET для действий — можно получить спам: forums.asp.net/t/1822324.aspx/1
В оправдание скажу только что это все таки интранет приложение и чз GET из браузера сделать ссылку проще. Но в целом- согласен. Можно подумать на тему- оставить только post.

Да уж, столько понаписали, и такой косяк с GET-ом. В этом вся Java — куча настраиваемых и конфигурируемых (недельки за три) компонентов, за которыми не видно базовых вещей.
GET здесь совершенно, ну совершенно не в кассу.
Объясню на пальцах: вы, вообще говоря, не контроллируете, как браузер или другой http-клиент будет использовать GET-методы, благо стандарт HTTP, особенно если его «вольно трактовать» и «оптимизировать», это позволяет. Клиент может, если это старый ИЕ или по дороге есть кривой прокси, вообще не вызывать GET — а фигли, закешировано же.
Ну и главное, конечно, безопасность. Отправляем вашему залогиненному юзеру например html емейл с картинкой <img src='ваш get url'> и всё — почта отправится «сама».
Для подобных действий — только POST. Для хардкорных html-кодеров есть формы, которые ничуть не сложнее просто ссылок; для js вызов get и post вообще ничем не отличаются, кроме названия метода.
UFO just landed and posted this here
Действительно- еще не пользовал. Но в данном случае- он неуместен:)
UFO just landed and posted this here
Всё же у JAX-RS и JAX-WS цели несколько разные. И когда нужна тонна энтерпрайзных стандартов WS-*, то REST нервно курит в сторонке, к сожалению.
UFO just landed and posted this here
Ну да, хоть и не соответствует идеологии REST, т. к. отнюдь не является идемпотентным действием.
UFO just landed and posted this here
Технически — да.

JAX-RS — это Java API for RESTful Web Services, так что идеологически JAX-RS предназначен для RESTful сервисов, что и задает рамки.

Поэтому REST-подход вполне ожидаем (и вызывает наименьшее удивление) при использовании JAX-RS. Я говорю исключительно про это.

А то, что в каком-то сервисе GET-запрос будет являться не идемпотентным, может дать дыру в безопасности — другая сторона вопроса.
UFO just landed and posted this here
Можно посмотреть и с этой стороны. Но в случае Java JAX-RS наиболее подходит для RESTful сервисов.

Как вспомню restlet'ы, аж передёргивает.
UFO just landed and posted this here
Аутентификацию прикрутить и всё.
Да и откуда инфа, что GET этот будет из браузера вызываться а не из какого-то клиента в качестве RESTful сервиса.
И заменить GET на POST в том коде — дело двух секунд.
Аутентификация не поможет, если юзер уже залогинен на атакуемом сервисе и все нужные куки у него уже стоят. Пример — в одной вкладке браузера открыт атакуемый сервис, в другой — веб-почта с веселыми картинками. CSRF токены тоже могут не справиться, хотя спасут хотя бы от повторяемых множественных атак (скажем, от десятка «картинок» в одном письме).
Смысл в том, что использовать в данном случае GET вместо POST — это всё равно, что использовать SELECT… PROCEDURE вместо INSERT для создания записей в базе данных или забивать шурупы молотком. Теоретически возможно, но абсолютно безграмотно.
Аутентификация есть. Но от ссылки в мыле залогиненного пользователя- не спасет. Post тоже есть в CommonServlet (см.
public class MailServlet extends CommonServlet). Запретить GET- ерунда. Только клиена UI еще придется с get на post перевести, ну и JUnit-ы поправить.
>А если сервер временно недоступен? Нужно сохранять историю в базе и делать доталкиватель…

Вообще-то с этого надо было начать — при нажатии кнопки «Отправить», создается запись в базе.
Ну а потом по базе бегает сервис, смотрит, что нужно отправить ну и отправляет.
Записывается в блоке final. С результатами отправки.
А тебе, пожалуйста, конкретное требование заказчика в студию.
Заказчик фичи- бизнес (аналитики).
Отправлять почту (желательно по веб-сервису). С доталкивателем.
+ позже — по ошибке из браузера клиента отправлять на support mail скриншот экрана.

Детали реализации, интеграция с имеющимися модулями и с окружением — тут уж я сам… Например, самому сходить за шаблоном: удобнее пользоваться будет, асинхронность, конфигурирование и пр.
Я теперь буду показывать эту статью людям, говорящим «ничего сложного» :)
А Вы все-таки смухлевали! Вместо apache commons надо было использовать труЪ JavaMail API ;))
Это вы к изобретению собственных велосипедов? Я сторонник пользоваться готовым.
Вы заглянули по первой ссылке… commons-email — удобные обертки к javamail.
Sign up to leave a comment.

Articles